// T3DLIB1.CPP - Game Engine Part I

// INCLUDES ///////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN  

// has the GUID library been included?
//#ifndef INITGUID
//#define INITGUID
//#endif

#define DEBUG_ON

#include <windows.h>   // include important windows stuff
#include <stdio.h>
#include <io.h>
#include <sys/timeb.h>
#include <time.h>
#include <math.h>

#include <ddraw.h>
#include "t3dlib1.h"

// DEFINES ////////////////////////////////////////////////

// TYPES //////////////////////////////////////////////////

// PROTOTYPES /////////////////////////////////////////////

// EXTERNALS /////////////////////////////////////////////

extern HWND main_window_handle; // save the window handle
extern HINSTANCE main_instance; // save the instance

// GLOBALS ////////////////////////////////////////////////

FILE *fp_error                    = NULL; // general error file
char error_filename[80];                  // error file name

// notice that interface 7.0 is used on a number of interfaces
LPDIRECTDRAW7        lpdd         = NULL;  // dd object
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL;  // dd primary surface
LPDIRECTDRAWSURFACE7 lpddsback    = NULL;  // dd back surface
LPDIRECTDRAWPALETTE  lpddpal      = NULL;  // a pointer to the created dd palette
LPDIRECTDRAWCLIPPER  lpddclipper  = NULL;   // dd clipper for back surface
LPDIRECTDRAWCLIPPER  lpddclipperwin = NULL; // dd clipper for window

PALETTEENTRY         palette[MAX_COLORS_PALETTE];         // color palette
PALETTEENTRY         save_palette[MAX_COLORS_PALETTE];    // used to save palettes
DDSURFACEDESC2       ddsd;                 // a direct draw surface description struct
DDBLTFX              ddbltfx;              // used to fill
DDSCAPS2             ddscaps;              // a direct draw surface capabilities struct
HRESULT              ddrval;               // result back from dd calls
UCHAR                *primary_buffer = NULL; // primary video buffer
UCHAR                *back_buffer    = NULL; // secondary back buffer
int                  primary_lpitch  = 0;    // memory line pitch for primary buffer
int                  back_lpitch     = 0;    // memory line pitch for back buffer
BITMAP_FILE          bitmap8bit;             // a 8 bit bitmap file
BITMAP_FILE          bitmap16bit;            // a 16 bit bitmap file
BITMAP_FILE          bitmap24bit;            // a 24 bit bitmap file

DWORD                start_clock_count = 0;     // used for timing
int                  windowed_mode     = FALSE; // tracks if dd is windowed or not

// these defined the general clipping rectangle
int min_clip_x = 0,                             // clipping rectangle 
	max_clip_x = (SCREEN_WIDTH-1),
	min_clip_y = 0,
	max_clip_y = (SCREEN_HEIGHT-1);

// these are overwritten globally by DDraw_Init()
int screen_width    = SCREEN_WIDTH,            // width of screen
	screen_height   = SCREEN_HEIGHT,           // height of screen
	screen_bpp      = SCREEN_BPP,              // bits per pixel
	screen_windowed = 0;                       // is this a windowed app?    

int dd_pixel_format = DD_PIXEL_FORMAT565;  // default pixel format

int window_client_x0   = 0;   // used to track the starting (x,y) client area for
int window_client_y0   = 0;   // for windowed mode directdraw operations

// storage for our lookup tables
float cos_look[361]; // 1 extra element so we can store 0-360 inclusive
float sin_look[361];

// function ptr to RGB16 builder
USHORT (*RGB16Bit)(int r, int g, int b) = NULL;
UINT(*RGB32Bit)(int a, int r, int g, int b) = NULL;
// conditionally compilation for engine stats
int debug_total_polys_per_frame = 0;
int debug_polys_lit_per_frame = 0;
int debug_polys_clipped_per_frame = 0;
int debug_polys_rendered_per_frame = 0;
int debug_backfaces_removed_per_frame = 0;
int debug_objects_removed_per_frame = 0;
int debug_total_transformations_per_frame = 0;

// FUNCTIONS //////////////////////////////////////////////

USHORT RGB16Bit565(int r, int g, int b)
{
	// this function simply builds a 5.6.5 format 16 bit pixel
	// assumes input is RGB 0-255 each channel
	r>>=3; g>>=2; b>>=3;
	return(_RGB16BIT565((r),(g),(b)));

} // end RGB16Bit565

UINT RGB32Bit888(int a,int r, int g, int b)
{
	// this function simply builds a 5.6.5 format 16 bit pixel
	// assumes input is RGB 0-255 each channel
	return(_RGB32BIT((a),(r),(g),(b)));

} // end RGB32Bit888
//////////////////////////////////////////////////////////

USHORT RGB16Bit555(int r, int g, int b)
{
	// this function simply builds a 5.5.5 format 16 bit pixel
	// assumes input is RGB 0-255 each channel
	r>>=3; g>>=3; b>>=3;
	return(_RGB16BIT555((r),(g),(b)));

} // end RGB16Bit555

//////////////////////////////////////////////////////////
#if 0 
inline void Mem_Set_WORD(void *dest, USHORT data, int count)
{
	// this function fills or sets unsigned 16-bit aligned memory
	// count is number of words

	//Write_Error("{");

	_asm 
	{ 
		mov edi, dest   ; edi points to destination memory
			mov ecx, count  ; number of 16-bit words to move
			mov ax,  data   ; 16-bit data
			rep stosw       ; move data
	} // end asm

	//Write_Error("}");

} // end Mem_Set_WORD

///////////////////////////////////////////////////////////

inline void Mem_Set_QUAD(void *dest, UINT data, int count)
{
	// this function fills or sets unsigned 32-bit aligned memory
	// count is number of quads

	_asm 
	{ 
		mov edi, dest   ; edi points to destination memory
			mov ecx, count  ; number of 32-bit words to move
			mov eax, data   ; 32-bit data
			rep stosd       ; move data
	} // end asm

} // end Mem_Set_QUAD
#endif
//////////////////////////////////////////////////////////

int Create_BOB(BOB_PTR bob,           // the bob to create
			   int x, int y,          // initial posiiton
			   int width, int height, // size of bob
			   int num_frames,        // number of frames
			   int attr,              // attrs
			   int mem_flags,         // memory flags in DD format
			   USHORT color_key_value, // default color key
			   int bpp)                // bits per pixel

{
	// Create the BOB object, note that all BOBs 
	// are created as offscreen surfaces in VRAM as the
	// default, if you want to use system memory then
	// set flags equal to:
	// DDSCAPS_SYSTEMMEMORY 
	// for video memory you can create either local VRAM surfaces or AGP 
	// surfaces via the second set of constants shown below in the regular expression
	// DDSCAPS_VIDEOMEMORY | (DDSCAPS_NONLOCALVIDMEM | DDSCAPS_LOCALVIDMEM ) 


	DDSURFACEDESC2 ddsd; // used to create surface
	int index;           // looping var

	// set state and attributes of BOB
	bob->state          = BOB_STATE_ALIVE;
	bob->attr           = attr;
	bob->anim_state     = 0;
	bob->counter_1      = 0;     
	bob->counter_2      = 0;
	bob->max_count_1    = 0;
	bob->max_count_2    = 0;

	bob->curr_frame     = 0;
	bob->num_frames     = num_frames;
	bob->bpp            = bpp;
	bob->curr_animation = 0;
	bob->anim_counter   = 0;
	bob->anim_index     = 0;
	bob->anim_count_max = 0; 
	bob->x              = x;
	bob->y              = y;
	bob->xv             = 0;
	bob->yv             = 0;

	// set dimensions of the new bitmap surface
	bob->width  = width;
	bob->height = height;

	// set all images to null
	for (index=0; index<MAX_BOB_FRAMES; index++)
		bob->images[index] = NULL;

	// set all animations to null
	for (index=0; index<MAX_BOB_ANIMATIONS; index++)
		bob->animations[index] = NULL;

#if 0
	// make sure surface width is a multiple of 8, some old version of dd like that
	// now, it's unneeded...
	bob->width_fill = ((width%8!=0) ? (8-width%8) : 0);
	Write_Error("\nCreate BOB: width_fill=%d",bob->width_fill);
#endif

	// now create each surface
	for (index=0; index<bob->num_frames; index++)
	{
		// set to access caps, width, and height
		memset(&ddsd,0,sizeof(ddsd));
		ddsd.dwSize  = sizeof(ddsd);
		ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

		ddsd.dwWidth  = bob->width + bob->width_fill;
		ddsd.dwHeight = bob->height;

		// set surface to offscreen plain
		ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

		// create the surfaces, return failure if problem
		if (FAILED(lpdd->CreateSurface(&ddsd,&(bob->images[index]),NULL)))
			return(0);

		// set color key to default color 000
		// note that if this is a 8bit bob then palette index 0 will be 
		// transparent by default
		// note that if this is a 16bit bob then RGB value 000 will be 
		// transparent
		DDCOLORKEY color_key; // used to set color key
		color_key.dwColorSpaceLowValue  = color_key_value;
		color_key.dwColorSpaceHighValue = color_key_value;

		// now set the color key for source blitting
		(bob->images[index])->SetColorKey(DDCKEY_SRCBLT, &color_key);

	} // end for index

	// return success
	return(1);

} // end Create_BOB

///////////////////////////////////////////////////////////

int Clone_BOB(BOB_PTR source, BOB_PTR dest)
{
	// this function clones a BOB and updates the attr var to reflect that
	// the BOB is a clone and not real, this is used later in the destroy
	// function so a clone doesn't destroy the memory of a real bob

	if ((source && dest) && (source!=dest))
	{
		// copy the bob data
		memcpy(dest,source, sizeof(BOB));

		// set the clone attribute
		dest->attr |= BOB_ATTR_CLONE;

	} // end if

	else
		return(0);

	// return success
	return(1);

} // end Clone_BOB

///////////////////////////////////////////////////////////

int Destroy_BOB(BOB_PTR bob)
{
	// destroy the BOB, tests if this is a real bob or a clone
	// if real then release all the memory, otherwise, just resets
	// the pointers to null

	int index; // looping var

	// is this bob valid
	if (!bob)
		return(0);

	// test if this is a clone
	if ((bob->attr & BOB_ATTR_CLONE))
	{
		// null link all surfaces
		for (index=0; index<MAX_BOB_FRAMES; index++)
			if (bob->images[index])
				bob->images[index]=NULL;

		// release memory for animation sequences 
		for (index=0; index<MAX_BOB_ANIMATIONS; index++)
			if (bob->animations[index])
				bob->animations[index]=NULL;

	} // end if
	else
	{
		// destroy each bitmap surface
		for (index=0; index<MAX_BOB_FRAMES; index++)
			if (bob->images[index])
				(bob->images[index])->Release();

		// release memory for animation sequences 
		for (index=0; index<MAX_BOB_ANIMATIONS; index++)
			if (bob->animations[index])
				free(bob->animations[index]);

	} // end else not clone

	// return success
	return(1);

} // end Destroy_BOB

///////////////////////////////////////////////////////////

int Draw_BOB(BOB_PTR bob,               // bob to draw
			 LPDIRECTDRAWSURFACE7 dest) // surface to draw the bob on
{
	// draw a bob at the x,y defined in the BOB
	// on the destination surface defined in dest

	RECT dest_rect,   // the destination rectangle
		source_rect; // the source rectangle                             

	// is this a valid bob
	if (!bob)
		return(0);

	// is bob visible
	if (!(bob->attr & BOB_ATTR_VISIBLE))
		return(1);

	// fill in the destination rect
	dest_rect.left   = bob->x;
	dest_rect.top    = bob->y;
	dest_rect.right  = bob->x+bob->width;
	dest_rect.bottom = bob->y+bob->height;

	// fill in the source rect
	source_rect.left    = 0;
	source_rect.top     = 0;
	source_rect.right   = bob->width;
	source_rect.bottom  = bob->height;

	// blt to destination surface
	if (FAILED(dest->Blt(&dest_rect, bob->images[bob->curr_frame],
		&source_rect,(DDBLT_WAIT | DDBLT_KEYSRC),
		NULL)))
		return(0);

	// return success
	return(1);
} // end Draw_BOB

///////////////////////////////////////////////////////////

int Draw_Scaled_BOB(BOB_PTR bob, int swidth, int sheight,  // bob and new dimensions
					LPDIRECTDRAWSURFACE7 dest) // surface to draw the bob on)
{
	// this function draws a scaled bob to the size swidth, sheight

	RECT dest_rect,   // the destination rectangle
		source_rect; // the source rectangle                             

	// is this a valid bob
	if (!bob)
		return(0);

	// is bob visible
	if (!(bob->attr & BOB_ATTR_VISIBLE))
		return(1);

	// fill in the destination rect
	dest_rect.left   = bob->x;
	dest_rect.top    = bob->y;
	dest_rect.right  = bob->x+swidth;
	dest_rect.bottom = bob->y+sheight;

	// fill in the source rect
	source_rect.left    = 0;
	source_rect.top     = 0;
	source_rect.right   = bob->width;
	source_rect.bottom  = bob->height;

	// blt to destination surface
	if (FAILED(dest->Blt(&dest_rect, bob->images[bob->curr_frame],
		&source_rect,(DDBLT_WAIT | DDBLT_KEYSRC),
		NULL)))
		return(0);

	// return success
	return(1);
} // end Draw_Scaled_BOB

////////////////////////////////////////////////////

int Draw_BOB16(BOB_PTR bob,             // bob to draw
			   LPDIRECTDRAWSURFACE7 dest) // surface to draw the bob on
{
	// draw a bob at the x,y defined in the BOB
	// on the destination surface defined in dest

	RECT dest_rect,   // the destination rectangle
		source_rect; // the source rectangle                             

	// is this a valid bob
	if (!bob)
		return(0);

	// is bob visible
	if (!(bob->attr & BOB_ATTR_VISIBLE))
		return(1);

	// fill in the destination rect
	dest_rect.left   = bob->x;
	dest_rect.top    = bob->y;
	dest_rect.right  = bob->x+bob->width;
	dest_rect.bottom = bob->y+bob->height;

	// fill in the source rect
	source_rect.left    = 0;
	source_rect.top     = 0;
	source_rect.right   = bob->width;
	source_rect.bottom  = bob->height;

	// blt to destination surface
	if (FAILED(dest->Blt(&dest_rect, bob->images[bob->curr_frame],
		&source_rect,(DDBLT_WAIT | DDBLT_KEYSRC),
		NULL)))
		return(0);

	// return success
	return(1);
} // end Draw_BOB16

///////////////////////////////////////////////////////////

int Draw_Scaled_BOB16(BOB_PTR bob, int swidth, int sheight,  // bob and new dimensions
					  LPDIRECTDRAWSURFACE7 dest) // surface to draw the bob on)
{
	// this function draws a scaled bob to the size swidth, sheight

	RECT dest_rect,   // the destination rectangle
		source_rect; // the source rectangle                             

	// is this a valid bob
	if (!bob)
		return(0);

	// is bob visible
	if (!(bob->attr & BOB_ATTR_VISIBLE))
		return(1);

	// fill in the destination rect
	dest_rect.left   = bob->x;
	dest_rect.top    = bob->y;
	dest_rect.right  = bob->x+swidth;
	dest_rect.bottom = bob->y+sheight;

	// fill in the source rect
	source_rect.left    = 0;
	source_rect.top     = 0;
	source_rect.right   = bob->width;
	source_rect.bottom  = bob->height;

	// blt to destination surface
	if (FAILED(dest->Blt(&dest_rect, bob->images[bob->curr_frame],
		&source_rect,(DDBLT_WAIT | DDBLT_KEYSRC),
		NULL)))
		return(0);

	// return success
	return(1);
} // end Draw_Scaled_BOB16

///////////////////////////////////////////////////////////

int Load_Frame_BOB(BOB_PTR bob, // bob to load with data
				   BITMAP_FILE_PTR bitmap, // bitmap to scan image data from
				   int frame,       // frame to load
				   int cx,int cy,   // cell or absolute pos. to scan image from
				   int mode)        // if 0 then cx,cy is cell position, else 
				   // cx,cy are absolute coords
{
	// this function extracts a bitmap out of a bitmap file

	DDSURFACEDESC2 ddsd;  //  direct draw surface description 

	// is this a valid bob
	if (!bob)
		return(0);

	UCHAR *source_ptr,   // working pointers
		*dest_ptr;

	// test the mode of extraction, cell based or absolute
	if (mode==BITMAP_EXTRACT_MODE_CELL)
	{
		// re-compute x,y
		cx = cx*(bob->width+1) + 1;
		cy = cy*(bob->height+1) + 1;
	} // end if

	// extract bitmap data
	source_ptr = bitmap->buffer + cy*bitmap->bitmapinfoheader.biWidth+cx;

	// get the addr to destination surface memory

	// set size of the structure
	ddsd.dwSize = sizeof(ddsd);

	// lock the display surface
	(bob->images[frame])->Lock(NULL,
		&ddsd,
		DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,
		NULL);

	// assign a pointer to the memory surface for manipulation
	dest_ptr = (UCHAR *)ddsd.lpSurface;

	// iterate thru each scanline and copy bitmap
	for (int index_y=0; index_y<bob->height; index_y++)
	{
		// copy next line of data to destination
		memcpy(dest_ptr, source_ptr,bob->width);

		// advance pointers
		dest_ptr   += (ddsd.lPitch); // (bob->width+bob->width_fill);
		source_ptr += bitmap->bitmapinfoheader.biWidth;
	} // end for index_y

	// unlock the surface 
	(bob->images[frame])->Unlock(NULL);

	// set state to loaded
	bob->attr |= BOB_ATTR_LOADED;

	// return success
	return(1);

} // end Load_Frame_BOB

///////////////////////////////////////////////////////////////

int Load_Frame_BOB16(BOB_PTR bob, // bob to load with data
					 BITMAP_FILE_PTR bitmap, // bitmap to scan image data from
					 int frame,       // frame to load
					 int cx,int cy,   // cell or absolute pos. to scan image from
					 int mode)        // if 0 then cx,cy is cell position, else 
					 // cx,cy are absolute coords
{
	// this function extracts a 16-bit bitmap out of a 16-bit bitmap file

	DDSURFACEDESC2 ddsd;  //  direct draw surface description 

	// is this a valid bob
	if (!bob)
		return(0);

	USHORT *source_ptr,   // working pointers
		*dest_ptr;

	// test the mode of extraction, cell based or absolute
	if (mode==BITMAP_EXTRACT_MODE_CELL)
	{
		// re-compute x,y
		cx = cx*(bob->width+1) + 1;
		cy = cy*(bob->height+1) + 1;
	} // end if

	// extract bitmap data
	source_ptr = (USHORT *)bitmap->buffer + cy*bitmap->bitmapinfoheader.biWidth+cx;

	// get the addr to destination surface memory

	// set size of the structure
	ddsd.dwSize = sizeof(ddsd);

	// lock the display surface
	(bob->images[frame])->Lock(NULL,
		&ddsd,
		DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,
		NULL);

	// assign a pointer to the memory surface for manipulation
	dest_ptr = (USHORT *)ddsd.lpSurface;

	// iterate thru each scanline and copy bitmap
	for (int index_y=0; index_y<bob->height; index_y++)
	{
		// copy next line of data to destination
		memcpy(dest_ptr, source_ptr,(bob->width*2));

		// advance pointers
		dest_ptr   += (ddsd.lPitch >> 1); 
		source_ptr += bitmap->bitmapinfoheader.biWidth;
	} // end for index_y

	// unlock the surface 
	(bob->images[frame])->Unlock(NULL);

	// set state to loaded
	bob->attr |= BOB_ATTR_LOADED;

	// return success
	return(1);

} // end Load_Frame_BOB16

///////////////////////////////////////////////////////////

int Animate_BOB(BOB_PTR bob)
{
	// this function animates a bob, basically it takes a look at
	// the attributes of the bob and determines if the bob is 
	// a single frame, multiframe, or multi animation, updates
	// the counters and frames appropriately

	// is this a valid bob
	if (!bob)
		return(0);

	// test the level of animation
	if (bob->attr & BOB_ATTR_SINGLE_FRAME)
	{
		// current frame always = 0
		bob->curr_frame = 0;
		return(1);
	} // end if
	else
		if (bob->attr & BOB_ATTR_MULTI_FRAME)
		{
			// update the counter and test if its time to increment frame
			if (++bob->anim_counter >= bob->anim_count_max)
			{
				// reset counter
				bob->anim_counter = 0;

				// move to next frame
				if (++bob->curr_frame >= bob->num_frames)
					bob->curr_frame = 0;

			} // end if

		} // end elseif
		else
			if (bob->attr & BOB_ATTR_MULTI_ANIM)
			{
				// this is the most complex of the animations it must look up the
				// next frame in the animation sequence

				// first test if its time to animate
				if (++bob->anim_counter >= bob->anim_count_max)
				{
					// reset counter
					bob->anim_counter = 0;

					// increment the animation frame index
					bob->anim_index++;

					// extract the next frame from animation list 
					bob->curr_frame = bob->animations[bob->curr_animation][bob->anim_index];

					// is this and end sequence flag -1
					if (bob->curr_frame == -1)
					{
						// test if this is a single shot animation
						if (bob->attr & BOB_ATTR_ANIM_ONE_SHOT)
						{
							// set animation state message to done
							bob->anim_state = BOB_STATE_ANIM_DONE;

							// reset frame back one
							bob->anim_index--;

							// extract animation frame
							bob->curr_frame = bob->animations[bob->curr_animation][bob->anim_index];    

						} // end if
						else
						{
							// reset animation index
							bob->anim_index = 0;

							// extract first animation frame
							bob->curr_frame = bob->animations[bob->curr_animation][bob->anim_index];
						} // end else

					}  // end if

				} // end if

			} // end elseif

			// return success
			return(1);

} // end Amimate_BOB

///////////////////////////////////////////////////////////

int Scroll_BOB(void)
{
	// this function scrolls a bob 
	// not implemented

	// return success
	return(1);
} // end Scroll_BOB

///////////////////////////////////////////////////////////

int Move_BOB(BOB_PTR bob)
{
	// this function moves the bob based on its current velocity
	// also, the function test for various motion attributes of the'
	// bob and takes the appropriate actions


	// is this a valid bob
	if (!bob)
		return(0);

	// translate the bob
	bob->x+=bob->xv;
	bob->y+=bob->yv;

	// test for wrap around
	if (bob->attr & BOB_ATTR_WRAPAROUND)
	{
		// test x extents first
		if (bob->x > max_clip_x)
			bob->x = min_clip_x - bob->width;
		else
			if (bob->x < min_clip_x-bob->width)
				bob->x = max_clip_x;

		// now y extents
		if (bob->x > max_clip_x)
			bob->x = min_clip_x - bob->width;
		else
			if (bob->x < min_clip_x-bob->width)
				bob->x = max_clip_x;

	} // end if
	else
		// test for bounce
		if (bob->attr & BOB_ATTR_BOUNCE)
		{
			// test x extents first
			if ((bob->x > max_clip_x - bob->width) || (bob->x < min_clip_x) )
				bob->xv = -bob->xv;

			// now y extents 
			if ((bob->y > max_clip_y - bob->height) || (bob->y < min_clip_y) )
				bob->yv = -bob->yv;

		} // end if

		// return success
		return(1);
} // end Move_BOB

///////////////////////////////////////////////////////////

int Load_Animation_BOB(BOB_PTR bob, 
					   int anim_index, 
					   int num_frames, 
					   int *sequence)
{
	// this function load an animation sequence for a bob
	// the sequence consists of frame indices, the function
	// will append a -1 to the end of the list so the display
	// software knows when to restart the animation sequence

	// is this bob valid
	if (!bob)
		return(0);

	// allocate memory for bob animation
	if (!(bob->animations[anim_index] = (int *)malloc((num_frames+1)*sizeof(int))))
		return(0);

	// load data into 
	for (int index=0; index<num_frames; index++)
		bob->animations[anim_index][index] = sequence[index];

	// set the end of the list to a -1
	bob->animations[anim_index][num_frames] = -1;

	// return success
	return(1);

} // end Load_Animation_BOB

///////////////////////////////////////////////////////////

int Set_Pos_BOB(BOB_PTR bob, int x, int y)
{
	// this functions sets the postion of a bob

	// is this a valid bob
	if (!bob)
		return(0);

	// set positin
	bob->x = x;
	bob->y = y;

	// return success
	return(1);
} // end Set_Pos_BOB

///////////////////////////////////////////////////////////

int Set_Anim_Speed_BOB(BOB_PTR bob,int speed)
{
	// this function simply sets the animation speed of a bob

	// is this a valid bob
	if (!bob)
		return(0);

	// set speed
	bob->anim_count_max = speed;

	// return success
	return(1);

} // end Set_Anim_Speed

///////////////////////////////////////////////////////////

int Set_Animation_BOB(BOB_PTR bob, int anim_index)
{
	// this function sets the animation to play

	// is this a valid bob
	if (!bob)
		return(0);

	// set the animation index
	bob->curr_animation = anim_index;

	// reset animation 
	bob->anim_index = 0;

	// return success
	return(1);

} // end Set_Animation_BOB

///////////////////////////////////////////////////////////

int Set_Vel_BOB(BOB_PTR bob,int xv, int yv)
{
	// this function sets the velocity of a bob

	// is this a valid bob
	if (!bob)
		return(0);

	// set velocity
	bob->xv = xv;
	bob->yv = yv;

	// return success
	return(1);
} // end Set_Vel_BOB

///////////////////////////////////////////////////////////

int Hide_BOB(BOB_PTR bob)
{
	// this functions hides bob 

	// is this a valid bob
	if (!bob)
		return(0);

	// reset the visibility bit
	RESET_BIT(bob->attr, BOB_ATTR_VISIBLE);

	// return success
	return(1);
} // end Hide_BOB

///////////////////////////////////////////////////////////

int Show_BOB(BOB_PTR bob)
{
	// this function shows a bob

	// is this a valid bob
	if (!bob)
		return(0);

	// set the visibility bit
	SET_BIT(bob->attr, BOB_ATTR_VISIBLE);

	// return success
	return(1);
} // end Show_BOB

///////////////////////////////////////////////////////////

int Collision_BOBS(BOB_PTR bob1, BOB_PTR bob2)
{
	// are these a valid bobs
	if (!bob1 || !bob2)
		return(0);

	// get the radi of each rect
	int width1  = (bob1->width>>1) - (bob1->width>>3);
	int height1 = (bob1->height>>1) - (bob1->height>>3);

	int width2  = (bob2->width>>1) - (bob2->width>>3);
	int height2 = (bob2->height>>1) - (bob2->height>>3);

	// compute center of each rect
	int cx1 = bob1->x + width1;
	int cy1 = bob1->y + height1;

	int cx2 = bob2->x + width2;
	int cy2 = bob2->y + height2;

	// compute deltas
	int dx = abs(cx2 - cx1);
	int dy = abs(cy2 - cy1);

	// test if rects overlap
	if (dx < (width1+width2) && dy < (height1+height2))
		return(1);
	else
		// else no collision
		return(0);

} // end Collision_BOBS

//////////////////////////////////////////////////////////

int DDraw_Init(int width, int height, int bpp, int windowed)
{
	// this function initializes directdraw
	int index; // looping variable

	// create IDirectDraw interface 7.0 object and test for error
	if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
		return(0);

	// based on windowed or fullscreen set coorperation level
	if (windowed)
	{
		// set cooperation level to windowed mode 
		if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,DDSCL_NORMAL)))
			return(0);

	} // end if
	else
	{
		// set cooperation level to fullscreen mode 
		if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
			DDSCL_ALLOWMODEX | DDSCL_FULLSCREEN | 
			DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT | DDSCL_MULTITHREADED )))
			return(0);

		// set the display mode
		if (FAILED(lpdd->SetDisplayMode(width,height,bpp,0,0)))
			return(0);

	} // end else

	// set globals
	screen_height   = height;
	screen_width    = width;
	screen_bpp      = bpp;
	screen_windowed = windowed;

	// Create the primary surface
	memset(&ddsd,0,sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);

	// we need to let dd know that we want a complex 
	// flippable surface structure, set flags for that
	if (!screen_windowed)
	{
		// fullscreen mode
		ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

		// set the backbuffer count to 0 for windowed mode
		// 1 for fullscreen mode, 2 for triple buffering
		ddsd.dwBackBufferCount = 1;
	} // end if
	else
	{
		// windowed mode
		ddsd.dwFlags = DDSD_CAPS;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

		// set the backbuffer count to 0 for windowed mode
		// 1 for fullscreen mode, 2 for triple buffering
		ddsd.dwBackBufferCount = 0;
	} // end else

	// create the primary surface
	lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL);

	// get the pixel format of the primary surface
	DDPIXELFORMAT ddpf; // used to get pixel format

	// initialize structure
	DDRAW_INIT_STRUCT(ddpf);

	// query the format from primary surface
	lpddsprimary->GetPixelFormat(&ddpf);

	// based on masks determine if system is 5.6.5 or 5.5.5
	//RGB Masks for 5.6.5 mode
	//DDPF_RGB  16 R: 0x0000F800  
	//             G: 0x000007E0  
	//             B: 0x0000001F  

	//RGB Masks for 5.5.5 mode
	//DDPF_RGB  16 R: 0x00007C00  
	//             G: 0x000003E0  
	//             B: 0x0000001F  
	// test for 6 bit green mask)
	//if (ddpf.dwGBitMask == 0x000007E0)
	//   dd_pixel_format = DD_PIXEL_FORMAT565;

	// use number of bits, better method
	dd_pixel_format = ddpf.dwRGBBitCount;

	Write_Error("\npixel format = %d",dd_pixel_format);

	// set up conversion macros, so you don't have to test which one to use
	if (dd_pixel_format == DD_PIXEL_FORMAT555)
	{
		RGB16Bit = RGB16Bit555;
		Write_Error("\npixel format = 5.5.5");
	} // end if
	else if (dd_pixel_format == DD_PIXEL_FORMATALPHA888)
	{
		RGB32Bit = RGB32Bit888;
	}
	else
	{
		RGB16Bit = RGB16Bit565;
		Write_Error("\npixel format = 5.6.5");
	} // end else

	// only need a backbuffer for fullscreen modes
	if (!screen_windowed)
	{
		// query for the backbuffer i.e the secondary surface
		ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

		if (FAILED(lpddsprimary->GetAttachedSurface(&ddscaps,&lpddsback)))
			return(0);

	} // end if
	else
	{
		// must be windowed, so create a double buffer that will be blitted
		// rather than flipped as in full screen mode
		lpddsback = DDraw_Create_Surface(width, height, DDSCAPS_SYSTEMMEMORY); // int mem_flags, USHORT color_key_flag);

	} // end else

	// create a palette only if 8bit mode
	if (screen_bpp==DD_PIXEL_FORMAT8)
	{
		// create and attach palette
		// clear all entries, defensive programming
		memset(palette,0,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

		// load a pre-made "good" palette off disk
		Load_Palette_From_File(DEFAULT_PALETTE_FILE, palette);

		// load and attach the palette, test for windowed mode
		if (screen_windowed)
		{
			// in windowed mode, so the first 10 and last 10 entries have
			// to be slightly modified as does the call to createpalette
			// reset the peFlags bit to PC_EXPLICIT for the "windows" colors
			for (index=0; index < 10; index++)
				palette[index].peFlags = palette[index+246].peFlags = PC_EXPLICIT;         

			// now create the palette object, but disable access to all 256 entries
			if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
				palette,&lpddpal,NULL)))
				return(0);

		} // end 
		else
		{
			// in fullscreen mode, so simple create the palette with the default palette
			// and fill in all 256 entries
			if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE | DDPCAPS_ALLOW256,
				palette,&lpddpal,NULL)))
				return(0);

		} // end if

		// now attach the palette to the primary surface
		if (FAILED(lpddsprimary->SetPalette(lpddpal)))
			return(0);

	} // end if attach palette for 8bit mode

	// clear out both primary and secondary surfaces
	if (screen_windowed)
	{
		// only clear backbuffer
		DDraw_Fill_Surface(lpddsback,0);
	} // end if
	else
	{
		// fullscreen, simply clear everything
		DDraw_Fill_Surface(lpddsprimary,0);
		DDraw_Fill_Surface(lpddsback,0);
	} // end else

	// set software algorithmic clipping region
	min_clip_x = 0;
	max_clip_x = screen_width - 1;
	min_clip_y = 0;
	max_clip_y = screen_height - 1;

	// setup backbuffer clipper always
	RECT screen_rect = {0,0,screen_width,screen_height};
	lpddclipper = DDraw_Attach_Clipper(lpddsback,1,&screen_rect);

	// set up windowed mode clipper
	if (screen_windowed)
	{
		// set windowed clipper
		if (FAILED(lpdd->CreateClipper(0,&lpddclipperwin,NULL)))
			return(0);

		if (FAILED(lpddclipperwin->SetHWnd(0, main_window_handle)))
			return(0);

		if (FAILED(lpddsprimary->SetClipper(lpddclipperwin)))
			return(0);
	} // end if screen windowed

	// return success
	return(1);

} // end DDraw_Init

///////////////////////////////////////////////////////////

int DDraw_Shutdown(void)
{
	// this function release all the resources directdraw
	// allocated, mainly to com objects

	// release the clippers first
	if (lpddclipper)
		lpddclipper->Release();

	if (lpddclipperwin)
		lpddclipperwin->Release();

	// release the palette if there is one
	if (lpddpal)
		lpddpal->Release();

	// release the secondary surface
	if (lpddsback)
		lpddsback->Release();

	// release the primary surface
	if (lpddsprimary)
		lpddsprimary->Release();

	// finally, the main dd object
	if (lpdd)
		lpdd->Release();

	// return success
	return(1);
} // end DDraw_Shutdown

///////////////////////////////////////////////////////////   

LPDIRECTDRAWCLIPPER DDraw_Attach_Clipper(LPDIRECTDRAWSURFACE7 lpdds,
										 int num_rects,
										 LPRECT clip_list)

{
	// this function creates a clipper from the sent clip list and attaches
	// it to the sent surface

	int index;                         // looping var
	LPDIRECTDRAWCLIPPER lpddclipper;   // pointer to the newly created dd clipper
	LPRGNDATA region_data;             // pointer to the region data that contains
	// the header and clip list

	// first create the direct draw clipper
	if (FAILED(lpdd->CreateClipper(0,&lpddclipper,NULL)))
		return(NULL);

	// now create the clip list from the sent data

	// first allocate memory for region data
	region_data = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER)+num_rects*sizeof(RECT));

	// now copy the rects into region data
	memcpy(region_data->Buffer, clip_list, sizeof(RECT)*num_rects);

	// set up fields of header
	region_data->rdh.dwSize          = sizeof(RGNDATAHEADER);
	region_data->rdh.iType           = RDH_RECTANGLES;
	region_data->rdh.nCount          = num_rects;
	region_data->rdh.nRgnSize        = num_rects*sizeof(RECT);

	region_data->rdh.rcBound.left    =  64000;
	region_data->rdh.rcBound.top     =  64000;
	region_data->rdh.rcBound.right   = -64000;
	region_data->rdh.rcBound.bottom  = -64000;

	// find bounds of all clipping regions
	for (index=0; index<num_rects; index++)
	{
		// test if the next rectangle unioned with the current bound is larger
		if (clip_list[index].left < region_data->rdh.rcBound.left)
			region_data->rdh.rcBound.left = clip_list[index].left;

		if (clip_list[index].right > region_data->rdh.rcBound.right)
			region_data->rdh.rcBound.right = clip_list[index].right;

		if (clip_list[index].top < region_data->rdh.rcBound.top)
			region_data->rdh.rcBound.top = clip_list[index].top;

		if (clip_list[index].bottom > region_data->rdh.rcBound.bottom)
			region_data->rdh.rcBound.bottom = clip_list[index].bottom;

	} // end for index

	// now we have computed the bounding rectangle region and set up the data
	// now let's set the clipping list

	if (FAILED(lpddclipper->SetClipList(region_data, 0)))
	{
		// release memory and return error
		free(region_data);
		return(NULL);
	} // end if

	// now attach the clipper to the surface
	if (FAILED(lpdds->SetClipper(lpddclipper)))
	{
		// release memory and return error
		free(region_data);
		return(NULL);
	} // end if

	// all is well, so release memory and send back the pointer to the new clipper
	free(region_data);
	return(lpddclipper);

} // end DDraw_Attach_Clipper

///////////////////////////////////////////////////////////   

LPDIRECTDRAWSURFACE7 DDraw_Create_Surface(int width, 
										  int height, 
										  int mem_flags, 
										  USHORT color_key_value)
{
	// this function creates an offscreen plain surface

	DDSURFACEDESC2 ddsd;         // working description
	LPDIRECTDRAWSURFACE7 lpdds;  // temporary surface

	// set to access caps, width, and height
	memset(&ddsd,0,sizeof(ddsd));
	ddsd.dwSize  = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

	// set dimensions of the new bitmap surface
	ddsd.dwWidth  =  width;
	ddsd.dwHeight =  height;

	// set surface to offscreen plain
	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

	// create the surface
	if (FAILED(lpdd->CreateSurface(&ddsd,&lpdds,NULL)))
		return(NULL);

	// set color key to default color 000
	// note that if this is a 8bit bob then palette index 0 will be 
	// transparent by default
	// note that if this is a 16bit bob then RGB value 000 will be 
	// transparent
	DDCOLORKEY color_key; // used to set color key
	color_key.dwColorSpaceLowValue  = color_key_value;
	color_key.dwColorSpaceHighValue = color_key_value;

	// now set the color key for source blitting
	lpdds->SetColorKey(DDCKEY_SRCBLT, &color_key);

	// return surface
	return(lpdds);
} // end DDraw_Create_Surface

///////////////////////////////////////////////////////////   

int DDraw_Flip(void)
{
	// this function flip the primary surface with the secondary surface

	// test if either of the buffers are locked
	if (primary_buffer || back_buffer)
		return(0);

	// flip pages
	if (!screen_windowed)
		while(FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
	else
	{
		RECT    dest_rect;    // used to compute destination rectangle

		// get the window's rectangle in screen coordinates
		GetWindowRect(main_window_handle, &dest_rect);   

		// compute the destination rectangle
		dest_rect.left   +=window_client_x0;
		dest_rect.top    +=window_client_y0;

		dest_rect.right  =dest_rect.left+screen_width-1;
		dest_rect.bottom =dest_rect.top +screen_height-1;

		// clip the screen coords 

		// blit the entire back surface to the primary
		if (FAILED(lpddsprimary->Blt(&dest_rect, lpddsback,NULL,DDBLT_WAIT,NULL)))
			return(0);    

	} // end if

	// return success
	return(1);

} // end DDraw_Flip

///////////////////////////////////////////////////////////   

int DDraw_Wait_For_Vsync(void)
{
	// this function waits for a vertical blank to begin

	lpdd->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,0);

	// return success
	return(1);
} // end DDraw_Wait_For_Vsync

///////////////////////////////////////////////////////////   

int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7 lpdds, USHORT color, RECT *client)
{
	DDBLTFX ddbltfx; // this contains the DDBLTFX structure

	// clear out the structure and set the size field 
	DDRAW_INIT_STRUCT(ddbltfx);

	// set the dwfillcolor field to the desired color
	ddbltfx.dwFillColor = color; 

	// ready to blt to surface
	lpdds->Blt(client,     // ptr to dest rectangle
		NULL,       // ptr to source surface, NA            
		NULL,       // ptr to source rectangle, NA
		DDBLT_COLORFILL | DDBLT_WAIT,   // fill and wait                   
		&ddbltfx);  // ptr to DDBLTFX structure

	// return success
	return(1);
} // end DDraw_Fill_Surface

///////////////////////////////////////////////////////////   

UCHAR *DDraw_Lock_Surface(LPDIRECTDRAWSURFACE7 lpdds, int *lpitch)
{
	// this function locks the sent surface and returns a pointer to it

	// is this surface valid
	if (!lpdds)
		return(NULL);

	// lock the surface
	DDRAW_INIT_STRUCT(ddsd);
	lpdds->Lock(NULL,&ddsd,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL); 

	// set the memory pitch
	if (lpitch)
		*lpitch = ddsd.lPitch;

	// return pointer to surface
	return((UCHAR *)ddsd.lpSurface);

} // end DDraw_Lock_Surface

///////////////////////////////////////////////////////////   

int DDraw_Unlock_Surface(LPDIRECTDRAWSURFACE7 lpdds)
{
	// this unlocks a general surface

	// is this surface valid
	if (!lpdds)
		return(0);

	// unlock the surface memory
	lpdds->Unlock(NULL);

	// return success
	return(1);
} // end DDraw_Unlock_Surface

///////////////////////////////////////////////////////////

UCHAR *DDraw_Lock_Primary_Surface(void)
{
	// this function locks the priamary surface and returns a pointer to it
	// and updates the global variables primary_buffer, and primary_lpitch

	// is this surface already locked
	if (primary_buffer)
	{
		// return to current lock
		return(primary_buffer);
	} // end if

	// lock the primary surface
	DDRAW_INIT_STRUCT(ddsd);
	lpddsprimary->Lock(NULL,&ddsd,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL); 

	// set globals
	primary_buffer = (UCHAR *)ddsd.lpSurface;
	primary_lpitch = ddsd.lPitch;

	// return pointer to surface
	return(primary_buffer);

} // end DDraw_Lock_Primary_Surface

///////////////////////////////////////////////////////////   

int DDraw_Unlock_Primary_Surface(void)
{
	// this unlocks the primary

	// is this surface valid
	if (!primary_buffer)
		return(0);

	// unlock the primary surface
	lpddsprimary->Unlock(NULL);

	// reset the primary surface
	primary_buffer = NULL;
	primary_lpitch = 0;

	// return success
	return(1);
} // end DDraw_Unlock_Primary_Surface

//////////////////////////////////////////////////////////

UCHAR *DDraw_Lock_Back_Surface(void)
{
	// this function locks the secondary back surface and returns a pointer to it
	// and updates the global variables secondary buffer, and back_lpitch

	// is this surface already locked
	if (back_buffer)
	{
		// return to current lock
		return(back_buffer);
	} // end if

	// lock the primary surface
	DDRAW_INIT_STRUCT(ddsd);
	lpddsback->Lock(NULL,&ddsd,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL); 

	// set globals
	back_buffer = (UCHAR *)ddsd.lpSurface;
	back_lpitch = ddsd.lPitch;

	// return pointer to surface
	return(back_buffer);

} // end DDraw_Lock_Back_Surface

///////////////////////////////////////////////////////////   

int DDraw_Unlock_Back_Surface(void)
{
	// this unlocks the secondary

	// is this surface valid
	if (!back_buffer)
		return(0);

	// unlock the secondary surface
	lpddsback->Unlock(NULL);

	// reset the secondary surface
	back_buffer = NULL;
	back_lpitch = 0;

	// return success
	return(1);
} // end DDraw_Unlock_Back_Surface

///////////////////////////////////////////////////////////

DWORD Get_Clock(void)
{
	// this function returns the current tick count

	// return time
	return(GetTickCount());

} // end Get_Clock

///////////////////////////////////////////////////////////

DWORD Start_Clock(void)
{
	// this function starts the clock, that is, saves the current
	// count, use in conjunction with Wait_Clock()

	return(start_clock_count = Get_Clock());

} // end Start_Clock

////////////////////////////////////////////////////////////

DWORD Wait_Clock(DWORD count)
{
	// this function is used to wait for a specific number of clicks
	// since the call to Start_Clock

	while((Get_Clock() - start_clock_count) < count);
	return(Get_Clock());

} // end Wait_Clock

///////////////////////////////////////////////////////////

int Draw_Clip_Line16(int x0,int y0, int x1, int y1, int color, 
					 UCHAR *dest_buffer, int lpitch)
{
	// this function draws a clipped line

	int cxs, cys,
		cxe, cye;

	// clip and draw each line
	cxs = x0;
	cys = y0;
	cxe = x1;
	cye = y1;

	// clip the line
	if (Clip_Line(cxs,cys,cxe,cye))
		Draw_Line16(cxs, cys, cxe,cye,color,dest_buffer,lpitch);

	// return success
	return(1);

} // end Draw_Clip_Line16


///////////////////////////////////////////////////////////
int Draw_Clip_Line32(int x0,int y0, int x1, int y1, int color, 
					 UCHAR *dest_buffer, int lpitch)
{
	// this function draws a clipped line

	int cxs, cys,
		cxe, cye;

	// clip and draw each line
	cxs = x0;
	cys = y0;
	cxe = x1;
	cye = y1;

	// clip the line
	if (Clip_Line(cxs,cys,cxe,cye))
		Draw_Line32(cxs, cys, cxe,cye,color,dest_buffer,lpitch);

	// return success
	return(1);

}
///////////////////////////////////////////////////////////

int Draw_Clip_Line(int x0,int y0, int x1, int y1, int color, 
				   UCHAR *dest_buffer, int lpitch)
{
	// this function draws a wireframe triangle

	int cxs, cys,
		cxe, cye;

	// clip and draw each line
	cxs = x0;
	cys = y0;
	cxe = x1;
	cye = y1;

	// clip the line
	if (Clip_Line(cxs,cys,cxe,cye))
		Draw_Line(cxs, cys, cxe,cye,color,dest_buffer,lpitch);

	// return success
	return(1);

} // end Draw_Clip_Line

///////////////////////////////////////////////////////////

int Clip_Line(int &x1,int &y1,int &x2, int &y2)
{
	// this function clips the sent line using the globally defined clipping
	// region

	// internal clipping codes
#define CLIP_CODE_C  0x0000
#define CLIP_CODE_N  0x0008
#define CLIP_CODE_S  0x0004
#define CLIP_CODE_E  0x0002
#define CLIP_CODE_W  0x0001

#define CLIP_CODE_NE 0x000a
#define CLIP_CODE_SE 0x0006
#define CLIP_CODE_NW 0x0009 
#define CLIP_CODE_SW 0x0005

	int xc1=x1, 
		yc1=y1, 
		xc2=x2, 
		yc2=y2;

	int p1_code=0, 
		p2_code=0;

	// determine codes for p1 and p2
	if (y1 < min_clip_y)
		p1_code|=CLIP_CODE_N;
	else
		if (y1 > max_clip_y)
			p1_code|=CLIP_CODE_S;

	if (x1 < min_clip_x)
		p1_code|=CLIP_CODE_W;
	else
		if (x1 > max_clip_x)
			p1_code|=CLIP_CODE_E;

	if (y2 < min_clip_y)
		p2_code|=CLIP_CODE_N;
	else
		if (y2 > max_clip_y)
			p2_code|=CLIP_CODE_S;

	if (x2 < min_clip_x)
		p2_code|=CLIP_CODE_W;
	else
		if (x2 > max_clip_x)
			p2_code|=CLIP_CODE_E;

	// try and trivially reject
	if ((p1_code & p2_code)) 
		return(0);

	// test for totally visible, if so leave points untouched
	if (p1_code==0 && p2_code==0)
		return(1);

	// determine end clip point for p1
	switch(p1_code)
	{
	case CLIP_CODE_C: break;

	case CLIP_CODE_N:
		{
			yc1 = min_clip_y;
			xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);
		} break;
	case CLIP_CODE_S:
		{
			yc1 = max_clip_y;
			xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);
		} break;

	case CLIP_CODE_W:
		{
			xc1 = min_clip_x;
			yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
		} break;

	case CLIP_CODE_E:
		{
			xc1 = max_clip_x;
			yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
		} break;

		// these cases are more complex, must compute 2 intersections
	case CLIP_CODE_NE:
		{
			// north hline intersection
			yc1 = min_clip_y;
			xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);

			// test if intersection is valid, of so then done, else compute next
			if (xc1 < min_clip_x || xc1 > max_clip_x)
			{
				// east vline intersection
				xc1 = max_clip_x;
				yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
			} // end if

		} break;

	case CLIP_CODE_SE:
		{
			// south hline intersection
			yc1 = max_clip_y;
			xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);	

			// test if intersection is valid, of so then done, else compute next
			if (xc1 < min_clip_x || xc1 > max_clip_x)
			{
				// east vline intersection
				xc1 = max_clip_x;
				yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
			} // end if

		} break;

	case CLIP_CODE_NW: 
		{
			// north hline intersection
			yc1 = min_clip_y;
			xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);

			// test if intersection is valid, of so then done, else compute next
			if (xc1 < min_clip_x || xc1 > max_clip_x)
			{
				xc1 = min_clip_x;
				yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);	
			} // end if

		} break;

	case CLIP_CODE_SW:
		{
			// south hline intersection
			yc1 = max_clip_y;
			xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);	

			// test if intersection is valid, of so then done, else compute next
			if (xc1 < min_clip_x || xc1 > max_clip_x)
			{
				xc1 = min_clip_x;
				yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);	
			} // end if

		} break;

	default:break;

	} // end switch

	// determine clip point for p2
	switch(p2_code)
	{
	case CLIP_CODE_C: break;

	case CLIP_CODE_N:
		{
			yc2 = min_clip_y;
			xc2 = x2 + (min_clip_y-y2)*(x1-x2)/(y1-y2);
		} break;

	case CLIP_CODE_S:
		{
			yc2 = max_clip_y;
			xc2 = x2 + (max_clip_y-y2)*(x1-x2)/(y1-y2);
		} break;

	case CLIP_CODE_W:
		{
			xc2 = min_clip_x;
			yc2 = y2 + (min_clip_x-x2)*(y1-y2)/(x1-x2);
		} break;

	case CLIP_CODE_E:
		{
			xc2 = max_clip_x;
			yc2 = y2 + (max_clip_x-x2)*(y1-y2)/(x1-x2);
		} break;

		// these cases are more complex, must compute 2 intersections
	case CLIP_CODE_NE:
		{
			// north hline intersection
			yc2 = min_clip_y;
			xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x)
			{
				// east vline intersection
				xc2 = max_clip_x;
				yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if

		} break;

	case CLIP_CODE_SE:
		{
			// south hline intersection
			yc2 = max_clip_y;
			xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);	

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x)
			{
				// east vline intersection
				xc2 = max_clip_x;
				yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
			} // end if

		} break;

	case CLIP_CODE_NW: 
		{
			// north hline intersection
			yc2 = min_clip_y;
			xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x)
			{
				xc2 = min_clip_x;
				yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);	
			} // end if

		} break;

	case CLIP_CODE_SW:
		{
			// south hline intersection
			yc2 = max_clip_y;
			xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);	

			// test if intersection is valid, of so then done, else compute next
			if (xc2 < min_clip_x || xc2 > max_clip_x)
			{
				xc2 = min_clip_x;
				yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);	
			} // end if

		} break;

	default:break;

	} // end switch

	// do bounds check
	if ((xc1 < min_clip_x) || (xc1 > max_clip_x) ||
		(yc1 < min_clip_y) || (yc1 > max_clip_y) ||
		(xc2 < min_clip_x) || (xc2 > max_clip_x) ||
		(yc2 < min_clip_y) || (yc2 > max_clip_y) )
	{
		return(0);
	} // end if

	// store vars back
	x1 = xc1;
	y1 = yc1;
	x2 = xc2;
	y2 = yc2;

	return(1);

} // end Clip_Line

///////////////////////////////////////////////////////////

int Draw_Line(int x0, int y0, // starting position 
			  int x1, int y1, // ending position
			  int color,     // color index
			  UCHAR *vb_start, int lpitch) // video buffer and memory pitch
{
	// this function draws a line from xo,yo to x1,y1 using differential error
	// terms (based on Bresenahams work)

	int dx,             // difference in x's
		dy,             // difference in y's
		dx2,            // dx,dy * 2
		dy2, 
		x_inc,          // amount in pixel space to move during drawing
		y_inc,          // amount in pixel space to move during drawing
		error,          // the discriminant i.e. error i.e. decision variable
		index;          // used for looping

	// pre-compute first pixel address in video buffer
	vb_start = vb_start + x0 + y0*lpitch;

	// compute horizontal and vertical deltas
	dx = x1-x0;
	dy = y1-y0;

	// test which direction the line is going in i.e. slope angle
	if (dx>=0)
	{
		x_inc = 1;

	} // end if line is moving right
	else
	{
		x_inc = -1;
		dx    = -dx;  // need absolute value

	} // end else moving left

	// test y component of slope

	if (dy>=0)
	{
		y_inc = lpitch;
	} // end if line is moving down
	else
	{
		y_inc = -lpitch;
		dy    = -dy;  // need absolute value

	} // end else moving up

	// compute (dx,dy) * 2
	dx2 = dx << 1;
	dy2 = dy << 1;

	// now based on which delta is greater we can draw the line
	if (dx > dy)
	{
		// initialize error term
		error = dy2 - dx; 

		// draw the line
		for (index=0; index <= dx; index++)
		{
			// set the pixel
			*vb_start = color;

			// test if error has overflowed
			if (error >= 0) 
			{
				error-=dx2;

				// move to next line
				vb_start+=y_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dy2;

			// move to the next pixel
			vb_start+=x_inc;

		} // end for

	} // end if |slope| <= 1
	else
	{
		// initialize error term
		error = dx2 - dy; 

		// draw the line
		for (index=0; index <= dy; index++)
		{
			// set the pixel
			*vb_start = color;

			// test if error overflowed
			if (error >= 0)
			{
				error-=dy2;

				// move to next line
				vb_start+=x_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dx2;

			// move to the next pixel
			vb_start+=y_inc;

		} // end for

	} // end else |slope| > 1

	// return success
	return(1);

} // end Draw_Line

///////////////////////////////////////////////////////////

int Draw_Line16(int x0, int y0, // starting position 
				int x1, int y1, // ending position
				int color,     // color index
				UCHAR *vb_start, int lpitch) // video buffer and memory pitch
{
	// this function draws a line from xo,yo to x1,y1 using differential error
	// terms (based on Bresenahams work)

	int dx,             // difference in x's
		dy,             // difference in y's
		dx2,            // dx,dy * 2
		dy2, 
		x_inc,          // amount in pixel space to move during drawing
		y_inc,          // amount in pixel space to move during drawing
		error,          // the discriminant i.e. error i.e. decision variable
		index;          // used for looping

	int lpitch_2 = lpitch >> 1; // USHORT strided lpitch

	// pre-compute first pixel address in video buffer based on 16bit data
	USHORT *vb_start2 = (USHORT *)vb_start + x0 + y0*lpitch_2;

	// compute horizontal and vertical deltas
	dx = x1-x0;
	dy = y1-y0;

	// test which direction the line is going in i.e. slope angle
	if (dx>=0)
	{
		x_inc = 1;

	} // end if line is moving right
	else
	{
		x_inc = -1;
		dx    = -dx;  // need absolute value

	} // end else moving left

	// test y component of slope

	if (dy>=0)
	{
		y_inc = lpitch_2;
	} // end if line is moving down
	else
	{
		y_inc = -lpitch_2;
		dy    = -dy;  // need absolute value

	} // end else moving up

	// compute (dx,dy) * 2
	dx2 = dx << 1;
	dy2 = dy << 1;

	// now based on which delta is greater we can draw the line
	if (dx > dy)
	{
		// initialize error term
		error = dy2 - dx; 

		// draw the line
		for (index=0; index <= dx; index++)
		{
			// set the pixel
			*vb_start2 = color;

			// test if error has overflowed
			if (error >= 0) 
			{
				error-=dx2;

				// move to next line
				vb_start2+=y_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dy2;

			// move to the next pixel
			vb_start2+=x_inc;

		} // end for

	} // end if |slope| <= 1
	else
	{
		// initialize error term
		error = dx2 - dy; 

		// draw the line
		for (index=0; index <= dy; index++)
		{
			// set the pixel
			*vb_start2 = color;

			// test if error overflowed
			if (error >= 0)
			{
				error-=dy2;

				// move to next line
				vb_start2+=x_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dx2;

			// move to the next pixel
			vb_start2+=y_inc;

		} // end for

	} // end else |slope| > 1

	// return success
	return(1);

} // end Draw_Line16

///////////////////////////////////////////////////////////

int Draw_Pixel(int x, int y,int color,
			   UCHAR *video_buffer, int lpitch)
{
	// this function plots a single pixel at x,y with color

	video_buffer[x + y*lpitch] = color;

	// return success
	return(1);

} // end Draw_Pixel

///////////////////////////////////////////////////////////   

int Draw_Pixel16(int x, int y,int color,
				 UCHAR *video_buffer, int lpitch)
{
	// this function plots a single pixel at x,y with color

	((USHORT *)video_buffer)[x + y*(lpitch >> 1)] = color;

	// return success
	return(1);

} // end Draw_Pixel16

///////////////////////////////////////////////////////////   
int Draw_Line32(int x0, int y0, // starting position 
				int x1, int y1, // ending position
				int color,     // color index
				UCHAR *vb_start, int lpitch) // video buffer and memory pitch
{
	// this function draws a line from xo,yo to x1,y1 using differential error
	// terms (based on Bresenahams work)

	int dx,             // difference in x's
		dy,             // difference in y's
		dx2,            // dx,dy * 2
		dy2, 
		x_inc,          // amount in pixel space to move during drawing
		y_inc,          // amount in pixel space to move during drawing
		error,          // the discriminant i.e. error i.e. decision variable
		index;          // used for looping

	int lpitch_2 = lpitch >> 2; // USHORT strided lpitch

	// pre-compute first pixel address in video buffer based on 16bit data
	UINT *vb_start2 = (UINT *)vb_start + x0 + y0*lpitch_2;

	// compute horizontal and vertical deltas
	dx = x1-x0;
	dy = y1-y0;

	// test which direction the line is going in i.e. slope angle
	if (dx>=0)
	{
		x_inc = 1;

	} // end if line is moving right
	else
	{
		x_inc = -1;
		dx    = -dx;  // need absolute value

	} // end else moving left

	// test y component of slope

	if (dy>=0)
	{
		y_inc = lpitch_2;
	} // end if line is moving down
	else
	{
		y_inc = -lpitch_2;
		dy    = -dy;  // need absolute value

	} // end else moving up

	// compute (dx,dy) * 2
	dx2 = dx << 1;
	dy2 = dy << 1;

	// now based on which delta is greater we can draw the line
	if (dx > dy)
	{
		// initialize error term
		error = dy2 - dx; 

		// draw the line
		for (index=0; index <= dx; index++)
		{
			// set the pixel
			*vb_start2 = color;

			// test if error has overflowed
			if (error >= 0) 
			{
				error-=dx2;

				// move to next line
				vb_start2+=y_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dy2;

			// move to the next pixel
			vb_start2+=x_inc;

		} // end for

	} // end if |slope| <= 1
	else
	{
		// initialize error term
		error = dx2 - dy; 

		// draw the line
		for (index=0; index <= dy; index++)
		{
			// set the pixel
			*vb_start2 = color;

			// test if error overflowed
			if (error >= 0)
			{
				error-=dy2;

				// move to next line
				vb_start2+=x_inc;

			} // end if error overflowed

			// adjust the error term
			error+=dx2;

			// move to the next pixel
			vb_start2+=y_inc;

		} // end for

	} // end else |slope| > 1

	// return success
	return(1);

} // end Draw_Line16

int Draw_Rectangle(int x1, int y1, int x2, int y2, int color,
				   LPDIRECTDRAWSURFACE7 lpdds)
{
	// this function uses directdraw to draw a filled rectangle

	DDBLTFX ddbltfx; // this contains the DDBLTFX structure
	RECT fill_area;  // this contains the destination rectangle

	// clear out the structure and set the size field 
	DDRAW_INIT_STRUCT(ddbltfx);

	// set the dwfillcolor field to the desired color
	ddbltfx.dwFillColor = color; 

	// fill in the destination rectangle data (your data)
	fill_area.top    = y1;
	fill_area.left   = x1;
	fill_area.bottom = y2;
	fill_area.right  = x2;

	// ready to blt to surface, in this case blt to primary
	lpdds->Blt(&fill_area, // ptr to dest rectangle
		NULL,       // ptr to source surface, NA            
		NULL,       // ptr to source rectangle, NA
		DDBLT_COLORFILL | DDBLT_WAIT,   // fill and wait                   
		&ddbltfx);  // ptr to DDBLTFX structure

	// return success
	return(1);

} // end Draw_Rectangle

///////////////////////////////////////////////////////////

int Set_Palette_Entry(int color_index, LPPALETTEENTRY color)
{
	// this function sets a palette color in the palette
	lpddpal->SetEntries(0,color_index,1,color);

	// set data in shadow palette
	memcpy(&palette[color_index],color,sizeof(PALETTEENTRY));

	// return success
	return(1);
} // end Set_Palette_Entry

///////////////////////////////////////////////////////////   

int Get_Palette_Entry(int color_index,LPPALETTEENTRY color)
{
	// this function retrieves a palette entry from the color
	// palette

	// copy data out from shadow palette
	memcpy(color, &palette[color_index],sizeof(PALETTEENTRY));

	// return success
	return(1);
} // end Get_Palette_Entry

///////////////////////////////////////////////////////////

int Load_Palette_From_File(char *filename, LPPALETTEENTRY palette)
{
	// this function loads a palette from disk into a palette
	// structure, but does not set the pallette

	FILE *fp_file; // working file

	// try and open file
	if ((fopen_s(&fp_file,filename,"r"))==NULL)
		return(0);

	// read in all 256 colors RGBF
	for (int index=0; index<MAX_COLORS_PALETTE; index++)
	{
		// read the next entry in
		fscanf_s(fp_file,"%d %d %d %d",&palette[index].peRed,
			&palette[index].peGreen,
			&palette[index].peBlue,                                
			&palette[index].peFlags);
	} // end for index

	// close the file
	fclose(fp_file);

	// return success
	return(1);
} // end Load_Palette_From_Disk

///////////////////////////////////////////////////////////   

int Save_Palette_To_File(char *filename, LPPALETTEENTRY palette)
{
	// this function saves a palette to disk

	FILE *fp_file; // working file

	// try and open file
	if ((fopen_s(&fp_file,filename,"w"))==NULL)
		return(0);

	// write in all 256 colors RGBF
	for (int index=0; index<MAX_COLORS_PALETTE; index++)
	{
		// read the next entry in
		fprintf(fp_file,"\n%d %d %d %d",palette[index].peRed,
			palette[index].peGreen,
			palette[index].peBlue,                                
			palette[index].peFlags);
	} // end for index

	// close the file
	fclose(fp_file);

	// return success
	return(1);

} // end Save_Palette_To_Disk

///////////////////////////////////////////////////////////

int Save_Palette(LPPALETTEENTRY sav_palette)
{
	// this function saves the current palette 

	memcpy(sav_palette, palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

	// return success
	return(1);
} // end Save_Palette

///////////////////////////////////////////////////////////   

int Set_Palette(LPPALETTEENTRY set_palette)
{
	// this function writes the sent palette

	// first save the new palette in shadow
	memcpy(palette, set_palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

	// now set the new palette
	lpddpal->SetEntries(0,0,MAX_COLORS_PALETTE,palette);

	// return success
	return(1);
} // end Set_Palette

///////////////////////////////////////////////////////////

int Rotate_Colors(int start_index, int end_index)
{
	// this function rotates the color between start and end

	int colors = end_index - start_index + 1;

	PALETTEENTRY work_pal[MAX_COLORS_PALETTE]; // working palette

	// get the color palette
	lpddpal->GetEntries(0,start_index,colors,work_pal);

	// shift the colors
	lpddpal->SetEntries(0,start_index+1,colors-1,work_pal);

	// fix up the last color
	lpddpal->SetEntries(0,start_index,1,&work_pal[colors - 1]);

	// update shadow palette
	lpddpal->GetEntries(0,0,MAX_COLORS_PALETTE,palette);

	// return success
	return(1);

} // end Rotate_Colors

///////////////////////////////////////////////////////////   

int Blink_Colors(int command, BLINKER_PTR new_light, int id)
{
	// this function blinks a set of lights

	static BLINKER lights[256]; // supports up to 256 blinking lights
	static int initialized = 0; // tracks if function has initialized

	// test if this is the first time function has ran
	if (!initialized)
	{
		// set initialized
		initialized = 1;

		// clear out all structures
		memset((void *)lights,0, sizeof(lights));

	} // end if

	// now test what command user is sending
	switch (command)
	{
	case BLINKER_ADD: // add a light to the database
		{
			// run thru database and find an open light
			for (int index=0; index < 256; index++)
			{
				// is this light available?
				if (lights[index].state == 0)
				{
					// set light up
					lights[index] = *new_light;

					// set internal fields up
					lights[index].counter =  0;
					lights[index].state   = -1; // off

					// update palette entry
					lpddpal->SetEntries(0,lights[index].color_index,1,&lights[index].off_color);

					// return id to caller
					return(index);

				} // end if

			} // end for index

		} break;

	case BLINKER_DELETE: // delete the light indicated by id
		{
			// delete the light sent in id 
			if (lights[id].state != 0)
			{
				// kill the light
				memset((void *)&lights[id],0,sizeof(BLINKER));

				// return id
				return(id);

			} // end if
			else
				return(-1); // problem


		} break;

	case BLINKER_UPDATE: // update the light indicated by id
		{ 
			// make sure light is active
			if (lights[id].state != 0)
			{
				// update on/off parms only
				lights[id].on_color  = new_light->on_color; 
				lights[id].off_color = new_light->off_color;
				lights[id].on_time   = new_light->on_time;
				lights[id].off_time  = new_light->off_time; 

				// update palette entry
				if (lights[id].state == -1)
					lpddpal->SetEntries(0,lights[id].color_index,1,&lights[id].off_color);
				else
					lpddpal->SetEntries(0,lights[id].color_index,1,&lights[id].on_color);

				// return id
				return(id);

			} // end if
			else
				return(-1); // problem

		} break;

	case BLINKER_RUN: // run the algorithm
		{
			// run thru database and process each light
			for (int index=0; index < 256; index++)
			{
				// is this active?
				if (lights[index].state == -1)
				{
					// update counter
					if (++lights[index].counter >= lights[index].off_time)
					{
						// reset counter
						lights[index].counter = 0;

						// change states 
						lights[index].state = -lights[index].state;               

						// update color
						lpddpal->SetEntries(0,lights[index].color_index,1,&lights[index].on_color);

					} // end if

				} // end if
				else
					if (lights[index].state == 1)
					{
						// update counter
						if (++lights[index].counter >= lights[index].on_time)
						{
							// reset counter
							lights[index].counter = 0;

							// change states 
							lights[index].state = -lights[index].state;               

							// update color
							lpddpal->SetEntries(0,lights[index].color_index,1,&lights[index].off_color);

						} // end if
					} // end else if

			} // end for index

		} break;

	default: break;

	} // end switch

	// return success
	return(1);

} // end Blink_Colors

///////////////////////////////////////////////////////////

int Draw_Text_GDI(char *text, int x,int y,COLORREF color, LPDIRECTDRAWSURFACE7 lpdds)
{
	// this function draws the sent text on the sent surface 
	// using color index as the color in the palette

	HDC xdc; // the working dc

	// get the dc from surface
	if (FAILED(lpdds->GetDC(&xdc)))
		return(0);

	// set the colors for the text up
	SetTextColor(xdc,color);

	// set background mode to transparent so black isn't copied
	SetBkMode(xdc, TRANSPARENT);

	// draw the text a
	TextOut(xdc,x,y,(LPCWSTR)text,strlen(text));

	// release the dc
	lpdds->ReleaseDC(xdc);

	// return success
	return(1);
} // end Draw_Text_GDI

///////////////////////////////////////////////////////////

int Draw_Text_GDI(char *text, int x,int y,int color, LPDIRECTDRAWSURFACE7 lpdds)
{
	// this function draws the sent text on the sent surface 
	// using color index as the color in the palette

	HDC xdc; // the working dc

	// get the dc from surface
	if (FAILED(lpdds->GetDC(&xdc)))
		return(0);

	// set the colors for the text up
	SetTextColor(xdc,RGB(palette[color].peRed,palette[color].peGreen,palette[color].peBlue) );

	// set background mode to transparent so black isn't copied
	SetBkMode(xdc, TRANSPARENT);

	// draw the text a
	TextOut(xdc,x,y,(LPCWSTR)text,strlen(text));

	// release the dc
	lpdds->ReleaseDC(xdc);

	// return success
	return(1);
} // end Draw_Text_GDI

///////////////////////////////////////////////////////////

int Open_Error_File(char *filename, FILE *fp_override)
{
	// this function creates the output error file

	// is user requesting special file handle? stdout, stderr, etc.?
	if (fp_override)
	{
		fp_error = fp_override;
	}
	else
	{
		// test if this file is valid
		if ((fopen_s(&fp_error,filename,"w"))==NULL)
			return(0);
	}

	// get the current time
	struct _timeb timebuffer;
	char timeline[128];
	char timestring[280];

	_ftime_s(&timebuffer);
	ctime_s(timeline,sizeof(timeline),&(timebuffer.time));

	sprintf_s(timestring, "%.19s.%hu, %s", timeline, timebuffer.millitm, &timeline[20]);

	// write out error header with time
	Write_Error("\nOpening Error Output File (%s) on %s\n",filename,timestring);

	// now the file is created, re-open with append mode

	if (!fp_override)
	{
		fclose(fp_error);
		if (  fopen_s(&fp_error,filename,"a+")==NULL)
			return(0);
	}

	// return success
	return(1);

} // end Open_Error_File

///////////////////////////////////////////////////////////

int Close_Error_File(void)
{
	// this function closes the error file

	if (fp_error)
	{
		// write close file string
		Write_Error("\nClosing Error Output File.");

		if (fp_error!=stdout || fp_error!=stderr)
		{
			// close the file handle
			fclose(fp_error);
		} 

		fp_error = NULL;

		// return success
		return(1);
	} // end if
	else
		return(0);

} // end Close_Error_File

///////////////////////////////////////////////////////////

int Write_Error(char *string, ...)
{
	// this function prints out the error string to the error file

	char buffer[1024]; // working buffer

	va_list arglist; // variable argument list

	// make sure both the error file and string are valid
	if (!string || !fp_error)
		return(0);

	// print out the string using the variable number of arguments on stack
	va_start(arglist,string);
	vsprintf_s(buffer,string,arglist);
	va_end(arglist);

	// write string to file
	fprintf(fp_error,buffer);

	// flush buffer incase the system bails
	fflush(fp_error);

	// return success
	return(1);
} // end Write_Error

///////////////////////////////////////////////////////////////////////////////

int Create_Bitmap(BITMAP_IMAGE_PTR image, int x, int y, int width, int height, int bpp)
{
	// this function is used to intialize a bitmap, 8 or 16 bit

	// allocate the memory
	if (!(image->buffer = (UCHAR *)malloc(width*height*(bpp>>3))))
		return(0);

	// initialize variables
	image->state     = BITMAP_STATE_ALIVE;
	image->attr      = 0;
	image->width     = width;
	image->height    = height;
	image->bpp       = bpp;
	image->x         = x;
	image->y         = y;
	image->num_bytes = width*height*(bpp>>3);

	// clear memory out
	memset(image->buffer,0,width*height*(bpp>>3));

	// return success
	return(1);

} // end Create_Bitmap

///////////////////////////////////////////////////////////////////////////////

int Destroy_Bitmap(BITMAP_IMAGE_PTR image)
{
	// this function releases the memory associated with a bitmap

	if (image && image->buffer)
	{
		// free memory and reset vars
		free(image->buffer);

		// set all vars in structure to 0
		memset(image,0,sizeof(BITMAP_IMAGE));

		// return success
		return(1);

	} // end if
	else  // invalid entry pointer of the object wasn't initialized
		return(0);

} // end Destroy_Bitmap

///////////////////////////////////////////////////////////

int Draw_Bitmap(BITMAP_IMAGE_PTR source_bitmap,UCHAR *dest_buffer, int lpitch, int transparent)
{
	// this function draws the bitmap onto the destination memory surface
	// if transparent is 1 then color 0 (8bit) or 0.0.0 (16bit) will be transparent
	// note this function does NOT clip, so be carefull!!!

	// test if this bitmap is loaded
	if (!(source_bitmap->attr & BITMAP_ATTR_LOADED))
		return(0);

	UCHAR *dest_addr,   // starting address of bitmap in destination
		*source_addr; // starting adddress of bitmap data in source

	UCHAR pixel;        // used to hold pixel value

	int index,          // looping vars
		pixel_x;

	// compute starting destination address
	dest_addr = dest_buffer + source_bitmap->y*lpitch + source_bitmap->x;

	// compute the starting source address
	source_addr = source_bitmap->buffer;

	// is this bitmap transparent
	if (transparent)
	{
		// copy each line of bitmap into destination with transparency
		for (index=0; index<source_bitmap->height; index++)
		{
			// copy the memory
			for (pixel_x=0; pixel_x<source_bitmap->width; pixel_x++)
			{
				if ((pixel = source_addr[pixel_x])!=0)
					dest_addr[pixel_x] = pixel;

			} // end if

			// advance all the pointers
			dest_addr   += lpitch;
			source_addr += source_bitmap->width;

		} // end for index
	} // end if
	else
	{
		// non-transparent version
		// copy each line of bitmap into destination

		for (index=0; index < source_bitmap->height; index++)
		{
			// copy the memory
			memcpy(dest_addr, source_addr, source_bitmap->width);

			// advance all the pointers
			dest_addr   += lpitch;
			source_addr += source_bitmap->width;

		} // end for index

	} // end else

	// return success
	return(1);

} // end Draw_Bitmap

///////////////////////////////////////////////////////////////

int Draw_Bitmap16(BITMAP_IMAGE_PTR source_bitmap,UCHAR *dest_buffer, int lpitch, int transparent)
{
	// this function draws the bitmap onto the destination memory surface
	// if transparent is 1 then color 0 (8bit) or 0.0.0 (16bit) will be transparent
	// note this function does NOT clip, so be carefull!!!

	// test if this bitmap is loaded
	if (!(source_bitmap->attr & BITMAP_ATTR_LOADED))
		return(0);

	USHORT *dest_addr,   // starting address of bitmap in destination
		*source_addr; // starting adddress of bitmap data in source

	USHORT pixel;        // used to hold pixel value

	int index,           // looping vars
		pixel_x,
		lpitch_2 = lpitch >> 1; // lpitch in USHORT terms

	// compute starting destination address
	dest_addr = ((USHORT *)dest_buffer) + source_bitmap->y*lpitch_2 + source_bitmap->x;

	// compute the starting source address
	source_addr = (USHORT *)source_bitmap->buffer;

	// is this bitmap transparent
	if (transparent)
	{
		// copy each line of bitmap into destination with transparency
		for (index=0; index<source_bitmap->height; index++)
		{
			// copy the memory
			for (pixel_x=0; pixel_x<source_bitmap->width; pixel_x++)
			{
				if ((pixel = source_addr[pixel_x])!=0)
					dest_addr[pixel_x] = pixel;

			} // end if

			// advance all the pointers
			dest_addr   += lpitch_2;
			source_addr += source_bitmap->width;

		} // end for index
	} // end if
	else
	{
		// non-transparent version
		// copy each line of bitmap into destination

		int source_bytes_per_line = source_bitmap->width*2;

		for (index=0; index < source_bitmap->height; index++)
		{
			// copy the memory
			memcpy(dest_addr, source_addr, source_bytes_per_line);

			// advance all the pointers
			dest_addr   += lpitch_2;
			source_addr += source_bitmap->width;

		} // end for index

	} // end else

	// return success
	return(1);

} // end Draw_Bitmap16

///////////////////////////////////////////////////////////////////////////////

int Load_Image_Bitmap(BITMAP_IMAGE_PTR image,  // bitmap image to load with data
					  BITMAP_FILE_PTR bitmap,  // bitmap to scan image data from
					  int cx,int cy,   // cell or absolute pos. to scan image from
					  int mode)        // if 0 then cx,cy is cell position, else 
					  // cx,cy are absolute coords
{
	// this function extracts a bitmap out of a bitmap file

	// is this a valid bitmap
	if (!image)
		return(0);

	UCHAR *source_ptr,   // working pointers
		*dest_ptr;

	// test the mode of extraction, cell based or absolute
	if (mode==BITMAP_EXTRACT_MODE_CELL)
	{
		// re-compute x,y
		cx = cx*(image->width+1) + 1;
		cy = cy*(image->height+1) + 1;
	} // end if

	// extract bitmap data
	source_ptr = bitmap->buffer +
		cy*bitmap->bitmapinfoheader.biWidth+cx;

	// assign a pointer to the bimap image
	dest_ptr = (UCHAR *)image->buffer;

	// iterate thru each scanline and copy bitmap
	for (int index_y=0; index_y<image->height; index_y++)
	{
		// copy next line of data to destination
		memcpy(dest_ptr, source_ptr,image->width);

		// advance pointers
		dest_ptr   += image->width;
		source_ptr += bitmap->bitmapinfoheader.biWidth;
	} // end for index_y

	// set state to loaded
	image->attr |= BITMAP_ATTR_LOADED;

	// return success
	return(1);

} // end Load_Image_Bitmap

///////////////////////////////////////////////////////////

int Load_Image_Bitmap16(BITMAP_IMAGE_PTR image,  // bitmap image to load with data
						BITMAP_FILE_PTR bitmap,  // bitmap to scan image data from
						int cx,int cy,   // cell or absolute pos. to scan image from
						int mode)        // if 0 then cx,cy is cell position, else 
						// cx,cy are absolute coords
{
	// this function extracts a 16-bit bitmap out of a 16-bit bitmap file

	// is this a valid bitmap
	if (!image)
		return(0);

	// must be a 16bit bitmap
	USHORT *source_ptr,   // working pointers
		*dest_ptr;

	// test the mode of extraction, cell based or absolute
	if (mode==BITMAP_EXTRACT_MODE_CELL)
	{
		// re-compute x,y
		cx = cx*(image->width+1) + 1;
		cy = cy*(image->height+1) + 1;
	} // end if

	// extract bitmap data
	source_ptr = (USHORT *)bitmap->buffer + 
		cy*bitmap->bitmapinfoheader.biWidth+cx;

	// assign a pointer to the bimap image
	dest_ptr = (USHORT *)image->buffer;

	int bytes_per_line = image->width*2;

	// iterate thru each scanline and copy bitmap
	for (int index_y=0; index_y < image->height; index_y++)
	{
		// copy next line of data to destination
		memcpy(dest_ptr, source_ptr,bytes_per_line);

		// advance pointers
		dest_ptr   += image->width;
		source_ptr += bitmap->bitmapinfoheader.biWidth;
	} // end for index_y

	// set state to loaded
	image->attr |= BITMAP_ATTR_LOADED;

	// return success
	return(1);

} // end Load_Image_Bitmap16

///////////////////////////////////////////////////////////

int Scroll_Bitmap(BITMAP_IMAGE_PTR image, int dx, int dy)
{
	// this function scrolls a bitmap

	BITMAP_IMAGE temp_image; // temp image buffer

	// are the parms valid 
	if (!image || (dx==0 && dy==0))
		return(0);


	// scroll on x-axis first
	if (dx!=0)
	{
		// step 1: normalize scrolling amount
		dx %= image->width;

		// step 2: which way?
		if (dx > 0)
		{
			// scroll right
			// create bitmap to hold region that is scrolled around
			Create_Bitmap(&temp_image, 0, 0, dx, image->height, image->bpp);

			// copy region we are going to scroll and wrap around
			Copy_Bitmap(&temp_image,0,0, 
				image, image->width-dx,0, 
				dx, image->height);

			// set some pointers up
			UCHAR *source_ptr = image->buffer;  // start of each line
			int shift         = (image->bpp >> 3)*dx;

			// now scroll image to right "scroll" pixels
			for (int y=0; y < image->height; y++)
			{
				// scroll the line over
				memmove(source_ptr+shift, source_ptr, (image->width-dx)*(image->bpp >> 3));

				// advance to the next line
				source_ptr+=((image->bpp >> 3)*image->width);
			} // end for

			// and now copy it back
			Copy_Bitmap(image, 0,0, &temp_image,0,0, 
				dx, image->height);           

		} // end if
		else
		{
			// scroll left
			dx = -dx; // invert sign

			// create bitmap to hold region that is scrolled around
			Create_Bitmap(&temp_image, 0, 0, dx, image->height, image->bpp);

			// copy region we are going to scroll and wrap around
			Copy_Bitmap(&temp_image,0,0, 
				image, 0,0, 
				dx, image->height);

			// set some pointers up
			UCHAR *source_ptr = image->buffer;  // start of each line
			int shift         = (image->bpp >> 3)*dx;

			// now scroll image to left "scroll" pixels
			for (int y=0; y < image->height; y++)
			{
				// scroll the line over
				memmove(source_ptr, source_ptr+shift, (image->width-dx)*(image->bpp >> 3));

				// advance to the next line
				source_ptr+=((image->bpp >> 3)*image->width);
			} // end for

			// and now copy it back
			Copy_Bitmap(image, image->width-dx,0, &temp_image,0,0, 
				dx, image->height);           

		} // end else
	} // end scroll on x-axis


	// return success
	return(1);

} // end Scroll_Bitmap

///////////////////////////////////////////////////////////

int Copy_Bitmap(BITMAP_IMAGE_PTR dest_bitmap, int dest_x, int dest_y, 
				BITMAP_IMAGE_PTR source_bitmap, int source_x, int source_y, 
				int width, int height)
{
	// this function copies a bitmap from one source to another

	// make sure the pointers are at least valid
	if (!dest_bitmap || !source_bitmap)
		return(0);

	// do some computations
	int bytes_per_pixel = (source_bitmap->bpp >> 3);

	// create some pointers
	UCHAR *source_ptr = source_bitmap->buffer + (source_x + source_y*source_bitmap->width)*bytes_per_pixel;
	UCHAR *dest_ptr   = dest_bitmap->buffer   + (dest_x   + dest_y  *dest_bitmap->width)  *bytes_per_pixel;

	// now copying is easy :)
	for (int y = 0; y < height; y++)
	{
		// copy this line
		memcpy(dest_ptr, source_ptr, bytes_per_pixel*width);

		// advance the pointers
		source_ptr+=(source_bitmap->width*bytes_per_pixel);
		dest_ptr  +=(dest_bitmap->width*bytes_per_pixel);
	} // end for

	// return success
	return(1);

} // end Copy_Bitmap

///////////////////////////////////////////////////////////

int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename)
{
	// this function opens a bitmap file and loads the data into bitmap

	int file_handle,  // the file handle
		index;        // looping index

	UCHAR   *temp_buffer = NULL; // used to convert 24 bit images to 16 bit
	OFSTRUCT file_data;          // the file data information

	// open the file if it exists
	if ((file_handle = OpenFile(filename,&file_data,OF_READ))==-1)
		return(0);

	// now load the bitmap file header
	_lread(file_handle, &bitmap->bitmapfileheader,sizeof(BITMAPFILEHEADER));

	// test if this is a bitmap file
	if (bitmap->bitmapfileheader.bfType!=BITMAP_ID)
	{
		// close the file
		_lclose(file_handle);

		// return error
		return(0);
	} // end if

	// now we know this is a bitmap, so read in all the sections

	// first the bitmap infoheader

	// now load the bitmap file header
	_lread(file_handle, &bitmap->bitmapinfoheader,sizeof(BITMAPINFOHEADER));

	// now load the color palette if there is one
	if (bitmap->bitmapinfoheader.biBitCount == 8)
	{
		_lread(file_handle, &bitmap->palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

		// now set all the flags in the palette correctly and fix the reversed 
		// BGR RGBQUAD data format
		for (index=0; index < MAX_COLORS_PALETTE; index++)
		{
			// reverse the red and green fields
			int temp_color                = bitmap->palette[index].peRed;
			bitmap->palette[index].peRed  = bitmap->palette[index].peBlue;
			bitmap->palette[index].peBlue = temp_color;

			// always set the flags word to this
			bitmap->palette[index].peFlags = PC_NOCOLLAPSE;
		} // end for index

	} // end if

	// finally the image data itself
	_lseek(file_handle,-(int)(bitmap->bitmapinfoheader.biSizeImage),SEEK_END);

	// now read in the image
	if (bitmap->bitmapinfoheader.biBitCount==8 || bitmap->bitmapinfoheader.biBitCount==16) 
	{
		// delete the last image if there was one
		if (bitmap->buffer)
			free(bitmap->buffer);

		// allocate the memory for the image
		if (!(bitmap->buffer = (UCHAR *)malloc(bitmap->bitmapinfoheader.biSizeImage)))
		{
			// close the file
			_lclose(file_handle);

			// return error
			return(0);
		} // end if

		// now read it in
		_lread(file_handle,bitmap->buffer,bitmap->bitmapinfoheader.biSizeImage);

	} // end if
	else
		if (bitmap->bitmapinfoheader.biBitCount==24)
		{
			// allocate temporary buffer to load 24 bit image
			if (!(temp_buffer = (UCHAR *)malloc(bitmap->bitmapinfoheader.biSizeImage)))
			{
				// close the file
				_lclose(file_handle);

				// return error
				return(0);
			} // end if

			// allocate final 16 bit storage buffer
			if (!(bitmap->buffer=(UCHAR *)malloc(2*bitmap->bitmapinfoheader.biWidth*bitmap->bitmapinfoheader.biHeight)))
			{
				// close the file
				_lclose(file_handle);

				// release working buffer
				free(temp_buffer);

				// return error
				return(0);
			} // end if

			// now read the file in
			_lread(file_handle,temp_buffer,bitmap->bitmapinfoheader.biSizeImage);

			// now convert each 24 bit RGB value into a 16 bit value
			for (index=0; index < bitmap->bitmapinfoheader.biWidth*bitmap->bitmapinfoheader.biHeight; index++)
			{
				// build up 16 bit color word
				USHORT color;

				// build pixel based on format of directdraw surface
				if (dd_pixel_format==DD_PIXEL_FORMAT555)
				{
					// extract RGB components (in BGR order), note the scaling
					UCHAR blue  = (temp_buffer[index*3 + 0] >> 3),
						green = (temp_buffer[index*3 + 1] >> 3),
						red   = (temp_buffer[index*3 + 2] >> 3); 
					// use the 555 macro
					color = _RGB16BIT555(red,green,blue);
				} // end if 555
				else
					if (dd_pixel_format==DD_PIXEL_FORMAT565) 
					{
						// extract RGB components (in BGR order), note the scaling
						UCHAR blue  = (temp_buffer[index*3 + 0] >> 3),
							green = (temp_buffer[index*3 + 1] >> 2),
							red   = (temp_buffer[index*3 + 2] >> 3);

						// use the 565 macro
						color = _RGB16BIT565(red,green,blue);

					} // end if 565

					// write color to buffer
					((USHORT *)bitmap->buffer)[index] = color;

			} // end for index

			// finally write out the correct number of bits
			bitmap->bitmapinfoheader.biBitCount=16;

			// release working buffer
			free(temp_buffer);

		} // end if 24 bit
		else
		{
			// serious problem
			return(0);

		} // end else

#if 0
		// write the file info out 
		printf("\nfilename:%s \nsize=%d \nwidth=%d \nheight=%d \nbitsperpixel=%d \ncolors=%d \nimpcolors=%d",
			filename,
			bitmap->bitmapinfoheader.biSizeImage,
			bitmap->bitmapinfoheader.biWidth,
			bitmap->bitmapinfoheader.biHeight,
			bitmap->bitmapinfoheader.biBitCount,
			bitmap->bitmapinfoheader.biClrUsed,
			bitmap->bitmapinfoheader.biClrImportant);
#endif

		// close the file
		_lclose(file_handle);

		// flip the bitmap
		Flip_Bitmap(bitmap->buffer, 
			bitmap->bitmapinfoheader.biWidth*(bitmap->bitmapinfoheader.biBitCount/8), 
			bitmap->bitmapinfoheader.biHeight);

		// return success
		return(1);

} // end Load_Bitmap_File

///////////////////////////////////////////////////////////

int Unload_Bitmap_File(BITMAP_FILE_PTR bitmap)
{
	// this function releases all memory associated with "bitmap"
	if (bitmap->buffer)
	{
		// release memory
		free(bitmap->buffer);

		// reset pointer
		bitmap->buffer = NULL;

	} // end if

	// return success
	return(1);

} // end Unload_Bitmap_File

///////////////////////////////////////////////////////////

int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height)
{
	// this function is used to flip bottom-up .BMP images

	UCHAR *buffer; // used to perform the image processing
	int index;     // looping index

	// allocate the temporary buffer
	if (!(buffer = (UCHAR *)malloc(bytes_per_line*height)))
		return(0);

	// copy image to work area
	memcpy(buffer,image,bytes_per_line*height);

	// flip vertically
	for (index=0; index < height; index++)
		memcpy(&image[((height-1) - index)*bytes_per_line],
		&buffer[index*bytes_per_line], bytes_per_line);

	// release the memory
	free(buffer);

	// return success
	return(1);

} // end Flip_Bitmap

///////////////////////////////////////////////////////////

void HLine16(int x1,int x2,int y,int color, UCHAR *vbuffer, int lpitch)
{
	// draw a horizontal line using the memset function

	int temp; // used for temporary storage during endpoint swap

	USHORT *vbuffer2 = (USHORT *)vbuffer; // short pointer to buffer

	// convert pitch to words
	lpitch = lpitch >> 1;

	// perform trivial rejections
	if (y > max_clip_y || y < min_clip_y)
		return;

	// sort x1 and x2, so that x2 > x1
	if (x1>x2)
	{
		temp = x1;
		x1   = x2;
		x2   = temp;
	} // end swap

	// perform trivial rejections
	if (x1 > max_clip_x || x2 < min_clip_x)
		return;

	// now clip
	x1 = ((x1 < min_clip_x) ? min_clip_x : x1);
	x2 = ((x2 > max_clip_x) ? max_clip_x : x2);

	// draw the row of pixels
	Mem_Set_WORD((vbuffer2+(y*lpitch)+x1), color,x2-x1+1);

} // end HLine16

//////////////////////////////////////////////////////////////////////////////

void VLine16(int y1,int y2,int x,int color,UCHAR *vbuffer, int lpitch)
{
	// draw a vertical line, note that a memset function can no longer be
	// used since the pixel addresses are no longer contiguous in memory
	// note that the end points of the line must be on the screen

	USHORT *start_offset; // starting memory offset of line

	int index, // loop index
		temp;  // used for temporary storage during swap

	// convert lpitch to number of words
	lpitch = lpitch >> 1;

	// perform trivial rejections
	if (x > max_clip_x || x < min_clip_x)
		return;

	// make sure y2 > y1
	if (y1>y2)
	{
		temp = y1;
		y1   = y2;
		y2   = temp;
	} // end swap

	// perform trivial rejections
	if (y1 > max_clip_y || y2 < min_clip_y)
		return;

	// now clip
	y1 = ((y1 < min_clip_y) ? min_clip_y : y1);
	y2 = ((y2 > max_clip_y) ? max_clip_y : y2);

	// compute starting position
	start_offset = (USHORT *)vbuffer + (y1*lpitch) + x;

	// draw line one pixel at a time
	for (index=0; index<=y2-y1; index++)
	{
		// set the pixel
		*start_offset = color;

		// move downward to next line
		start_offset+=lpitch;

	} // end for index

} // end VLine16

///////////////////////////////////////////////////////////

void HLine(int x1,int x2,int y,int color, UCHAR *vbuffer, int lpitch)
{
	// draw a horizontal line using the memset function

	int temp; // used for temporary storage during endpoint swap

	// perform trivial rejections
	if (y > max_clip_y || y < min_clip_y)
		return;

	// sort x1 and x2, so that x2 > x1
	if (x1>x2)
	{
		temp = x1;
		x1   = x2;
		x2   = temp;
	} // end swap

	// perform trivial rejections
	if (x1 > max_clip_x || x2 < min_clip_x)
		return;

	// now clip
	x1 = ((x1 < min_clip_x) ? min_clip_x : x1);
	x2 = ((x2 > max_clip_x) ? max_clip_x : x2);

	// draw the row of pixels
	memset((UCHAR *)(vbuffer+(y*lpitch)+x1),
		(UCHAR)color,x2-x1+1);

} // end HLine

//////////////////////////////////////////////////////////////////////////////

void VLine(int y1,int y2,int x,int color,UCHAR *vbuffer, int lpitch)
{
	// draw a vertical line, note that a memset function can no longer be
	// used since the pixel addresses are no longer contiguous in memory
	// note that the end points of the line must be on the screen

	UCHAR *start_offset; // starting memory offset of line

	int index, // loop index
		temp;  // used for temporary storage during swap


	// perform trivial rejections
	if (x > max_clip_x || x < min_clip_x)
		return;

	// make sure y2 > y1
	if (y1>y2)
	{
		temp = y1;
		y1   = y2;
		y2   = temp;
	} // end swap

	// perform trivial rejections
	if (y1 > max_clip_y || y2 < min_clip_y)
		return;

	// now clip
	y1 = ((y1 < min_clip_y) ? min_clip_y : y1);
	y2 = ((y2 > max_clip_y) ? max_clip_y : y2);

	// compute starting position
	start_offset = vbuffer + (y1*lpitch) + x;

	// draw line one pixel at a time
	for (index=0; index<=y2-y1; index++)
	{
		// set the pixel
		*start_offset = (UCHAR)color;

		// move downward to next line
		start_offset+=lpitch;

	} // end for index

} // end VLine

///////////////////////////////////////////////////////////

void Screen_Transitions(int effect, UCHAR *vbuffer, int lpitch)
{
	// this function can be called to perform a myraid of screen transitions
	// to the destination buffer, make sure to save and restore the palette
	// when performing color transitions in 8-bit modes

	int pal_reg;         // used as loop counter
	int index;           // used as loop counter
	int red,green,blue;           // used in fad algorithm

	PALETTEENTRY color;              // temporary color
	PALETTEENTRY work_palette[MAX_COLORS_PALETTE];  // used as a working palette
	PALETTEENTRY work_color;         // used in color algorithms

	// test which screen effect is being selected
	switch(effect)
	{
	case SCREEN_DARKNESS:
		{
			// fade to black

			for (index=0; index<80; index++)
			{
				// get the palette 
				Save_Palette(work_palette);

				// process each color
				for (pal_reg=1; pal_reg<MAX_COLORS_PALETTE; pal_reg++)
				{
					// get the entry data
					color = work_palette[pal_reg];

					// test if this color register is already black
					if (color.peRed > 4) color.peRed-=3;
					else
						color.peRed = 0;

					if (color.peGreen > 4) color.peGreen-=3;
					else
						color.peGreen = 0;

					if (color.peBlue  > 4) color.peBlue-=3;
					else
						color.peBlue = 0;

					// set the color to a diminished intensity
					work_palette[pal_reg] = color;

				} // end for pal_reg

				// write the palette back out
				Set_Palette(work_palette);

				// wait a bit

				Start_Clock(); Wait_Clock(12);

			} // end for index

		} break;

	case SCREEN_WHITENESS:
		{
			// fade to white
			for (index=0; index<64; index++)
			{
				// get the palette 
				Save_Palette(work_palette);

				// loop thru all palette registers
				for (pal_reg=0; pal_reg < MAX_COLORS_PALETTE; pal_reg++)
				{
					// get the entry data
					color = work_palette[pal_reg];

					// make 32 bit copy of color
					red   = color.peRed;
					green = color.peGreen;
					blue  = color.peBlue; 

					if ((red+=4) >=255)
						red=255;

					if ((green+=4) >=255)
						green=255;

					if ((blue+=4) >=255)
						blue=255;

					// store colors back
					color.peRed   = red;
					color.peGreen = green;
					color.peBlue  = blue;

					// set the color to a diminished intensity
					work_palette[pal_reg] = color;

				} // end for pal_reg

				// write the palette back out
				Set_Palette(work_palette);

				// wait a bit

				Start_Clock(); Wait_Clock(12);

			} // end for index

		} break;

	case SCREEN_REDNESS:
		{
			// fade to red

			for (index=0; index<64; index++)
			{
				// get the palette 
				Save_Palette(work_palette);

				// loop thru all palette registers
				for (pal_reg=0; pal_reg < MAX_COLORS_PALETTE; pal_reg++)
				{
					// get the entry data
					color = work_palette[pal_reg];

					// make 32 bit copy of color
					red   = color.peRed;
					green = color.peGreen;
					blue  = color.peBlue; 

					if ((red+=6) >=255)
						red=255; 

					if ((green-=4) < 0)
						green=0;

					if ((blue-=4) < 0)
						blue=0;

					// store colors back
					color.peRed   = red;
					color.peGreen = green;
					color.peBlue  = blue;

					// set the color to a diminished intensity
					work_palette[pal_reg] = color;

				} // end for pal_reg

				// write the palette back out
				Set_Palette(work_palette);

				// wait a bit

				Start_Clock(); Wait_Clock(12);

			} // end for index

		} break;

	case SCREEN_BLUENESS:
		{
			// fade to blue

			for (index=0; index<64; index++)
			{
				// get the palette 
				Save_Palette(work_palette);

				// loop thru all palette registers
				for (pal_reg=0; pal_reg < MAX_COLORS_PALETTE; pal_reg++)
				{
					// get the entry data
					color = work_palette[pal_reg];

					// make 32 bit copy of color
					red   = color.peRed;
					green = color.peGreen;
					blue  = color.peBlue; 

					if ((red-=4) < 0)
						red=0;

					if ((green-=4) < 0)
						green=0;

					if ((blue+=6) >=255)
						blue=255;

					// store colors back
					color.peRed   = red;
					color.peGreen = green;
					color.peBlue  = blue;

					// set the color to a diminished intensity
					work_palette[pal_reg] = color;

				} // end for pal_reg

				// write the palette back out
				Set_Palette(work_palette);

				// wait a bit

				Start_Clock(); Wait_Clock(12);

			} // end for index

		} break;

	case SCREEN_GREENNESS:
		{
			// fade to green
			for (index=0; index<64; index++)
			{
				// get the palette 
				Save_Palette(work_palette);

				// loop thru all palette registers
				for (pal_reg=0; pal_reg < MAX_COLORS_PALETTE; pal_reg++)
				{
					// get the entry data
					color = work_palette[pal_reg];                  

					// make 32 bit copy of color
					red   = color.peRed;
					green = color.peGreen;
					blue  = color.peBlue; 

					if ((red-=4) < 0)
						red=0;

					if ((green+=6) >=255)
						green=255;

					if ((blue-=4) < 0)
						blue=0;

					// store colors back
					color.peRed   = red;
					color.peGreen = green;
					color.peBlue  = blue;

					// set the color to a diminished intensity
					work_palette[pal_reg] = color; 

				} // end for pal_reg

				// write the palette back out
				Set_Palette(work_palette);

				// wait a bit

				Start_Clock(); Wait_Clock(12);


			} // end for index

		} break;

	case SCREEN_SWIPE_X:
		{
			// do a screen wipe from right to left, left to right
			for (index=0; index < (screen_width/2); index+=2)
			{
				// use this as a 1/70th of second time delay

				Start_Clock(); Wait_Clock(12);

				// test screen depth
				if (screen_bpp==8)
				{    
					// draw two vertical lines at opposite ends of the screen
					VLine(0,(screen_height-1),(screen_width-1)-index,0,vbuffer,lpitch);
					VLine(0,(screen_height-1),index,0,vbuffer,lpitch);
					VLine(0,(screen_height-1),(screen_width-1)-(index+1),0,vbuffer,lpitch);
					VLine(0,(screen_height-1),index+1,0,vbuffer,lpitch);
				} // end if 8-bit mode
				else
					if (screen_bpp==16)
					{    
						// 16-bit mode draw two vertical lines at opposite ends of the screen
						VLine16(0,(screen_height-1),(screen_width-1)-index,0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),index,0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),(screen_width-1)-(index+1),0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),index+1,0,vbuffer,lpitch);
					} // end if 16-bit mode


			} // end for index

		} break;

	case SCREEN_SWIPE_Y:
		{
			// do a screen wipe from top to bottom, bottom to top
			for (index=0; index < (screen_height/2); index+=2)
			{
				// use this as a 1/70th of second time delay

				Start_Clock(); Wait_Clock(12);

				// test screen depth             
				if (screen_bpp==8)
				{
					// draw two horizontal lines at opposite ends of the screen
					HLine(0,(screen_width-1),(screen_height-1)-index,0,vbuffer,lpitch);
					HLine(0,(screen_width-1),index,0,vbuffer,lpitch);
					HLine(0,(screen_width-1),(screen_height-1)-(index+1),0,vbuffer,lpitch);
					HLine(0,(screen_width-1),index+1,0,vbuffer,lpitch);
				} // end if 8-bit mode
				else 
					if (screen_bpp==16)
					{
						// draw two horizontal lines at opposite ends of the screen
						HLine16(0,(screen_width-1),(screen_height-1)-index,0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),index,0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),(screen_height-1)-(index+1),0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),index+1,0,vbuffer,lpitch);
					} // end if 16-bit mode

			} // end for index


		} break;

	case SCREEN_SCRUNCH:
		{
			// do a screen wipe from top to bottom, bottom to top
			for (index=0; index < (screen_width/2); index+=2)
			{
				// use this as a 1/70th of second time delay

				Start_Clock(); Wait_Clock(12);

				// test screen depth             
				if (screen_bpp==8)
				{ 
					// draw two horizontal lines at opposite ends of the screen
					HLine(0,(screen_width-1),(screen_height-1)-index%(screen_height/2),0,vbuffer,lpitch);
					HLine(0,(screen_width-1),index%(screen_height/2),0,vbuffer,lpitch);
					HLine(0,(screen_width-1),(screen_height-1)-(index%(screen_height/2)+1),0,vbuffer,lpitch);
					HLine(0,(screen_width-1),index%(screen_height/2)+1,0,vbuffer,lpitch);

					// draw two vertical lines at opposite ends of the screen
					VLine(0,(screen_height-1),(screen_width-1)-index,0,vbuffer,lpitch);
					VLine(0,(screen_height-1),index,0,vbuffer,lpitch);
					VLine(0,(screen_height-1),(screen_width-1)-(index+1),0,vbuffer,lpitch);
					VLine(0,(screen_height-1),index+1,0,vbuffer,lpitch);
				} // end if 8-bit mode
				else
					// test screen depth             
					if (screen_bpp==16)
					{ 
						// draw two horizontal lines at opposite ends of the screen
						HLine16(0,(screen_width-1),(screen_height-1)-index%(screen_height/2),0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),index%(screen_height/2),0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),(screen_height-1)-(index%(screen_height/2)+1),0,vbuffer,lpitch);
						HLine16(0,(screen_width-1),index%(screen_height/2)+1,0,vbuffer,lpitch);

						// draw two vertical lines at opposite ends of the screen
						VLine16(0,(screen_height-1),(screen_width-1)-index,0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),index,0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),(screen_width-1)-(index+1),0,vbuffer,lpitch);
						VLine16(0,(screen_height-1),index+1,0,vbuffer,lpitch);
					} // end if 8-bit mode

			} // end for index

		} break;


	case SCREEN_DISOLVE:
		{
			// disolve the screen by plotting zillions of little black dots

			if (screen_bpp==8)
				for (index=0; index<=screen_width*screen_height*4; index++)
					Draw_Pixel(rand()%screen_width,rand()%screen_height,0,vbuffer,lpitch);
			else
				if (screen_bpp==16)
					for (index=0; index<=screen_width*screen_height*4; index++)
						Draw_Pixel16(rand()%screen_width,rand()%screen_height,0,vbuffer,lpitch);

		} break;

	default:break;

	} // end switch

} // end Screen_Transitions

//////////////////////////////////////////////////////////////////////////////

int Collision_Test(int x1, int y1, int w1, int h1, 
				   int x2, int y2, int w2, int h2) 
{
	// this function tests if the two rects overlap

	// get the radi of each rect
	int width1  = (w1>>1) - (w1>>3);
	int height1 = (h1>>1) - (h1>>3);

	int width2  = (w2>>1) - (w2>>3);
	int height2 = (h2>>1) - (h2>>3);

	// compute center of each rect
	int cx1 = x1 + width1;
	int cy1 = y1 + height1;

	int cx2 = x2 + width2;
	int cy2 = y2 + height2;

	// compute deltas
	int dx = abs(cx2 - cx1);
	int dy = abs(cy2 - cy1);

	// test if rects overlap
	if (dx < (width1+width2) && dy < (height1+height2))
		return(1);
	else
		// else no collision
		return(0);

} // end Collision_Test

///////////////////////////////////////////////////////////

int Color_Scan(int x1, int y1, int x2, int y2, 
			   UCHAR scan_start, UCHAR scan_end,
			   UCHAR *scan_buffer, int scan_lpitch)
{
	// this function implements a crude collision technique
	// based on scanning for a range of colors within a rectangle

	// clip rectangle

	// x coords first    
	if (x1 >= screen_width)
		x1=screen_width-1;
	else
		if (x1 < 0)
			x1=0;

	if (x2 >= screen_width)
		x2=screen_width-1;
	else
		if (x2 < 0)
			x2=0;

	// now y-coords
	if (y1 >= screen_height)
		y1=screen_height-1;
	else
		if (y1 < 0)
			y1=0;

	if (y2 >= screen_height)
		y2=screen_height-1;
	else
		if (y2 < 0)
			y2=0;

	// scan the region
	scan_buffer +=y1*scan_lpitch;

	for (int scan_y=y1; scan_y<=y2; scan_y++)
	{
		for (int scan_x=x1; scan_x<=x2; scan_x++)
		{
			if (scan_buffer[scan_x] >= scan_start && scan_buffer[scan_x] <= scan_end )
				return(1);
		} // end for x

		// move down a line
		scan_buffer+=scan_lpitch;

	} // end for y

	// return failure
	return(0);

} // end Color_Scan

////////////////////////////////////////////////////////////////

int Color_Scan16(int x1, int y1, int x2, int y2, 
				 USHORT scan_start, USHORT scan_end,
				 UCHAR *scan_buffer, int scan_lpitch)
{
	// this function implements a crude collision technique
	// based on scanning for a range of colors within a rectangle
	// this is the 16-bit version, thus the interpretation of scan_start
	// and end are different, they are they EXACT RGB values you are looking
	// for, thus you can test for 2 values at most, else make them equal to
	// test for one value
	USHORT *scan_buffer2 = (USHORT *)scan_buffer;

	// convert number of bytes per line to number of 16-bit shorts
	scan_lpitch = (scan_lpitch >> 1);

	// clip rectangle

	// x coords first    
	if (x1 >= screen_width)
		x1=screen_width-1;
	else
		if (x1 < 0)
			x1=0;

	if (x2 >= screen_width)
		x2=screen_width-1;
	else
		if (x2 < 0)
			x2=0;

	// now y-coords
	if (y1 >= screen_height)
		y1=screen_height-1;
	else
		if (y1 < 0)
			y1=0;

	if (y2 >= screen_height)
		y2=screen_height-1;
	else
		if (y2 < 0)
			y2=0;

	// scan the region
	scan_buffer2 +=y1*scan_lpitch;

	for (int scan_y=y1; scan_y<=y2; scan_y++)
	{
		for (int scan_x=x1; scan_x<=x2; scan_x++)
		{
			if (scan_buffer2[scan_x] == scan_start || scan_buffer2[scan_x] == scan_end )
				return(1);
		} // end for x

		// move down a line
		scan_buffer2+=scan_lpitch;

	} // end for y

	// return failure
	return(0);

} // end Color_Scan16

////////////////////////////////////////////////////////////////

int Scan_Image_Bitmap(BITMAP_FILE_PTR bitmap,     // bitmap file to scan image data from
					  LPDIRECTDRAWSURFACE7 lpdds, // surface to hold data
					  int cx,int cy)              // cell to scan image from
{
	// this function extracts a bitmap out of a bitmap file

	UCHAR *source_ptr,   // working pointers
		*dest_ptr;

	DDSURFACEDESC2 ddsd;  //  direct draw surface description 

	// get the addr to destination surface memory

	// set size of the structure
	ddsd.dwSize = sizeof(ddsd);

	// lock the display surface
	lpdds->Lock(NULL,
		&ddsd,
		DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,
		NULL);

	// compute position to start scanning bits from
	cx = cx*(ddsd.dwWidth+1) + 1;
	cy = cy*(ddsd.dwHeight+1) + 1;

	// extract bitmap data
	source_ptr = bitmap->buffer + cy*bitmap->bitmapinfoheader.biWidth+cx;

	// assign a pointer to the memory surface for manipulation
	dest_ptr = (UCHAR *)ddsd.lpSurface;

	// iterate thru each scanline and copy bitmap
	for (int index_y=0; index_y < ddsd.dwHeight; index_y++)
	{
		// copy next line of data to destination
		memcpy(dest_ptr, source_ptr, ddsd.dwWidth);

		// advance pointers
		dest_ptr   += (ddsd.lPitch);
		source_ptr += bitmap->bitmapinfoheader.biWidth;
	} // end for index_y

	// unlock the surface 
	lpdds->Unlock(NULL);

	// return success
	return(1);

} // end Scan_Image_Bitmap

//////////////////////////////////////////////////////////////////////////////

void Draw_Top_Tri(int x1,int y1, 
				  int x2,int y2, 
				  int x3,int y3,
				  int color, 
				  UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle that has a flat top
	mempitch >>= 2;
	float dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	// destination address of next scanline
	UINT  *dest_addr = NULL;

	// test order of x1 and x2
	if (x2 < x1)
	{
		temp_x = x2;
		x2     = x1;
		x1     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x3-x1)/height;
	dx_right = (x3-x2)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x2+(float)0.5;

	// perform y clipping
	if (y1 < min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+min_clip_y);
		xe = xe+dx_right*(float)(-y1+min_clip_y);

		// reset y1
		y1=min_clip_y;

	} // end if top is off screen

	if (y3>max_clip_y)
		y3=max_clip_y;

	// compute starting address in video memory
	dest_addr = (UINT*)dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			Mem_Set_QUAD((UINT*)dest_addr+(unsigned int)xs, color,(unsigned int)((int)xe-(int)xs+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}

			Mem_Set_QUAD((UINT*)dest_addr+(unsigned int)left, color,(unsigned int)(right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Top_Tri

/////////////////////////////////////////////////////////////////////////////

void Draw_Bottom_Tri(int x1,int y1, 
					 int x2,int y2, 
					 int x3,int y3,
					 int color,
					 UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle that has a flat bottom
	mempitch >>= 2;
	float dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	// destination address of next scanline
	UINT  *dest_addr;

	// test order of x1 and x2
	if (x3 < x2)
	{
		temp_x = x2;
		x2     = x3;
		x3     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x2-x1)/height;
	dx_right = (x3-x1)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x1; // +(float)0.5;

	// perform y clipping
	if (y1<min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+min_clip_y);
		xe = xe+dx_right*(float)(-y1+min_clip_y);

		// reset y1
		y1=min_clip_y;

	} // end if top is off screen

	if (y3>max_clip_y)
		y3=max_clip_y;

	// compute starting address in video memory
	dest_addr = (UINT*)dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			Mem_Set_QUAD((UINT*)dest_addr+(unsigned int)xs, color,(unsigned int)((int)xe-(int)xs+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle

		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}

			Mem_Set_QUAD((UINT*)dest_addr+(unsigned int)left, color,(unsigned int)(right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Bottom_Tri

///////////////////////////////////////////////////////////////////////////////

void Draw_Top_Tri16(int x1,int y1, 
					int x2,int y2, 
					int x3,int y3,
					int color, 
					UCHAR *_dest_buffer, int mempitch)
{
	// this function draws a triangle that has a flat top

	float dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	// cast dest buffer to ushort
	USHORT *dest_buffer = (USHORT *)_dest_buffer;

	// destination address of next scanline
	USHORT  *dest_addr = NULL;

	// recompute mempitch in 16-bit words
	mempitch = (mempitch >> 1);

	// test order of x1 and x2
	if (x2 < x1)
	{
		temp_x = x2;
		x2     = x1;
		x1     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x3-x1)/height;
	dx_right = (x3-x2)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x2; // +(float)0.5;

	// perform y clipping
	if (y1 < min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+min_clip_y);
		xe = xe+dx_right*(float)(-y1+min_clip_y);

		// reset y1
		y1=min_clip_y;

	} // end if top is off screen

	if (y3>max_clip_y)
		y3=max_clip_y;

	// compute starting address in video memory
	dest_addr = dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// draw the line
			Mem_Set_WORD(dest_addr+(unsigned int)(xs),color,(unsigned int)((int)xe-(int)xs+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}

			// draw the line
			Mem_Set_WORD(dest_addr+(unsigned int)(left),color,(unsigned int)(right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Top_Tri16

/////////////////////////////////////////////////////////////////////////////

void Draw_Bottom_Tri16(int x1,int y1, 
					   int x2,int y2, 
					   int x3,int y3,
					   int color,
					   UCHAR *_dest_buffer, int mempitch)
{
	// this function draws a triangle that has a flat bottom

	float dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	// cast dest buffer to ushort
	USHORT *dest_buffer = (USHORT *)_dest_buffer;

	// destination address of next scanline
	USHORT  *dest_addr = NULL;

	// recompute mempitch in 16-bit words
	mempitch = (mempitch >> 1);

	// test order of x1 and x2
	if (x3 < x2)
	{
		temp_x = x2;
		x2     = x3;
		x3     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x2-x1)/height;
	dx_right = (x3-x1)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x1; // +(float)0.5;

	// perform y clipping
	if (y1 < min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+min_clip_y);
		xe = xe+dx_right*(float)(-y1+min_clip_y);

		// reset y1
		y1 = min_clip_y;

	} // end if top is off screen

	if (y3 > max_clip_y)
		y3 = max_clip_y;

	// compute starting address in video memory
	dest_addr = dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// draw the line
			Mem_Set_WORD(dest_addr+(unsigned int)(xs),color,(unsigned int)((int)xe-(int)xs+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}
			// draw the line
			Mem_Set_WORD(dest_addr+(unsigned int)(left),color,(unsigned int)(right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Bottom_Tri16

///////////////////////////////////////////////////////////////////////////////

void Draw_TriangleFP_2D(int x1,int y1,
						int x2,int y2,
						int x3,int y3,
						int color,
						UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle on the destination buffer using fixed point
	// it decomposes all triangles into a pair of flat top, flat bottom

	int temp_x, // used for sorting
		temp_y,
		new_x;

#ifdef DEBUG_ON
	// track rendering stats
	debug_polys_rendered_per_frame++;
#endif


	// test for h lines and v lines
	if ((x1==x2 && x2==x3)  ||  (y1==y2 && y2==y3))
		return;

	// sort p1,p2,p3 in ascending y order
	if (y2<y1)
	{
		temp_x = x2;
		temp_y = y2;
		x2     = x1;
		y2     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// now we know that p1 and p2 are in order
	if (y3<y1)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x1;
		y3     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// finally test y3 against y2
	if (y3<y2)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x2;
		y3     = y2;
		x2     = temp_x;
		y2     = temp_y;

	} // end if

	// do trivial rejection tests for clipping
	if ( y3<min_clip_y || y1>max_clip_y ||
		(x1<min_clip_x && x2<min_clip_x && x3<min_clip_x) ||
		(x1>max_clip_x && x2>max_clip_x && x3>max_clip_x) )
		return;

	// test if top of triangle is flat
	if (y1==y2)
	{
		Draw_Top_TriFP(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
	} // end if
	else
		if (y2==y3)
		{
			Draw_Bottom_TriFP(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
		} // end if bottom is flat
		else
		{
			// general triangle that's needs to be broken up along long edge
			new_x = x1 + (int)(0.5+(float)(y2-y1)*(float)(x3-x1)/(float)(y3-y1));

			// draw each sub-triangle
			Draw_Bottom_TriFP(x1,y1,new_x,y2,x2,y2,color, dest_buffer, mempitch);
			Draw_Top_TriFP(x2,y2,new_x,y2,x3,y3,color, dest_buffer, mempitch);

		} // end else

} // end Draw_TriangleFP_2D

/////////////////////////////////////////////////////////////

void Draw_Triangle_2D(int x1,int y1,
					  int x2,int y2,
					  int x3,int y3,
					  int color,
					  UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle on the destination buffer
	// it decomposes all triangles into a pair of flat top, flat bottom

	int temp_x, // used for sorting
		temp_y,
		new_x;

#ifdef DEBUG_ON
	// track rendering stats
	debug_polys_rendered_per_frame++;
#endif


	// test for h lines and v lines
	if ((x1==x2 && x2==x3)  ||  (y1==y2 && y2==y3))
		return;

	// sort p1,p2,p3 in ascending y order
	if (y2<y1)
	{
		temp_x = x2;
		temp_y = y2;
		x2     = x1;
		y2     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// now we know that p1 and p2 are in order
	if (y3<y1)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x1;
		y3     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// finally test y3 against y2
	if (y3<y2)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x2;
		y3     = y2;
		x2     = temp_x;
		y2     = temp_y;

	} // end if

	// do trivial rejection tests for clipping
	if ( y3<min_clip_y || y1>max_clip_y ||
		(x1<min_clip_x && x2<min_clip_x && x3<min_clip_x) ||
		(x1>max_clip_x && x2>max_clip_x && x3>max_clip_x) )
		return;

	// test if top of triangle is flat
	if (y1==y2)
	{
		Draw_Top_Tri(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
	} // end if
	else
		if (y2==y3)
		{
			Draw_Bottom_Tri(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
		} // end if bottom is flat
		else
		{
			// general triangle that's needs to be broken up along long edge
			new_x = x1 + (int)(0.5+(float)(y2-y1)*(float)(x3-x1)/(float)(y3-y1));

			// draw each sub-triangle
			Draw_Bottom_Tri(x1,y1,new_x,y2,x2,y2,color, dest_buffer, mempitch);
			Draw_Top_Tri(x2,y2,new_x,y2,x3,y3,color, dest_buffer, mempitch);

		} // end else

} // end Draw_Triangle_2D

///////////////////////////////////////////////////////////////////////////////

void Draw_Triangle_2D16(int x1,int y1,
						int x2,int y2,
						int x3,int y3,
						int color,
						UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle on the destination buffer
	// it decomposes all triangles into a pair of flat top, flat bottom

	int temp_x, // used for sorting
		temp_y,
		new_x;

#ifdef DEBUG_ON
	// track rendering stats
	debug_polys_rendered_per_frame++;
#endif


	// test for h lines and v lines
	if ((x1==x2 && x2==x3)  ||  (y1==y2 && y2==y3))
		return;

	// sort p1,p2,p3 in ascending y order
	if (y2<y1)
	{
		temp_x = x2;
		temp_y = y2;
		x2     = x1;
		y2     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// now we know that p1 and p2 are in order
	if (y3<y1)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x1;
		y3     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// finally test y3 against y2
	if (y3<y2)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x2;
		y3     = y2;
		x2     = temp_x;
		y2     = temp_y;

	} // end if

	// do trivial rejection tests for clipping
	if ( y3<min_clip_y || y1>max_clip_y ||
		(x1<min_clip_x && x2<min_clip_x && x3<min_clip_x) ||
		(x1>max_clip_x && x2>max_clip_x && x3>max_clip_x) )
		return;

	// test if top of triangle is flat
	if (y1==y2)
	{
		Draw_Top_Tri16(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
	} // end if
	else
		if (y2==y3)
		{
			Draw_Bottom_Tri16(x1,y1,x2,y2,x3,y3,color, dest_buffer, mempitch);
		} // end if bottom is flat
		else
		{
			// general triangle that's needs to be broken up along long edge
			new_x = x1 + (int)(0.5+(float)(y2-y1)*(float)(x3-x1)/(float)(y3-y1));

			// draw each sub-triangle
			Draw_Bottom_Tri16(x1,y1,new_x,y2,x2,y2,color, dest_buffer, mempitch);
			Draw_Top_Tri16(x2,y2,new_x,y2,x3,y3,color, dest_buffer, mempitch);

		} // end else

} // end Draw_Triangle_2D16

////////////////////////////////////////////////////////////////////////////////
#if 0
inline void Draw_QuadFP_2D(int x0,int y0,
						   int x1,int y1,
						   int x2,int y2,
						   int x3, int y3,
						   int color,
						   UCHAR *dest_buffer, int mempitch)
{
	// this function draws a 2D quadrilateral

	// simply call the triangle function 2x, let it do all the work
	Draw_TriangleFP_2D(x0,y0,x1,y1,x3,y3,color,dest_buffer,mempitch);
	Draw_TriangleFP_2D(x1,y1,x2,y2,x3,y3,color,dest_buffer,mempitch);

} // end Draw_QuadFP_2D
#endif
////////////////////////////////////////////////////////////////////////////////

void Draw_Top_TriFP(int x1,int y1,
					int x2,int y2, 
					int x3,int y3,
					int color, 
					UCHAR *dest_buffer, int mempitch)
{
	// this function draws a triangle that has a flat top using fixed point math

	int dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	UCHAR  *dest_addr;

	// test for degenerate
	if (y1==y3 || y2==y3)
		return;

	// test order of x1 and x2
	if (x2 < x1)
	{
		temp_x = x2;
		x2     = x1;
		x1     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = ((x3-x1)<<FIXP16_SHIFT)/height;
	dx_right = ((x3-x2)<<FIXP16_SHIFT)/height;

	// set starting points
	xs = (x1<<FIXP16_SHIFT);
	xe = (x2<<FIXP16_SHIFT);

	// perform y clipping
	if (y1<min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(-y1+min_clip_y);
		xe = xe+dx_right*(-y1+min_clip_y);

		// reset y1
		y1=min_clip_y;

	} // end if top is off screen

	if (y3>max_clip_y)
		y3=max_clip_y;

	// compute starting address in video memory
	dest_addr = dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			memset((UCHAR *)dest_addr+((xs+FIXP16_ROUND_UP)>>FIXP16_SHIFT),
				color, (((xe-xs+FIXP16_ROUND_UP)>>FIXP16_SHIFT)+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = ((xs+FIXP16_ROUND_UP)>>16);
			right = ((xe+FIXP16_ROUND_UP)>>16);

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}

			memset((UCHAR  *)dest_addr+(unsigned int)left,
				color,(unsigned int)(right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Top_TriFP

/////////////////////////////////////////////////////////////////////////////

void Draw_Bottom_TriFP(int x1,int y1, 
					   int x2,int y2, 
					   int x3,int y3,
					   int color,
					   UCHAR *dest_buffer, int mempitch)
{

	// this function draws a triangle that has a flat bottom using fixed point math

	int dx_right,    // the dx/dy ratio of the right edge of line
		dx_left,     // the dx/dy ratio of the left edge of line
		xs,xe,       // the starting and ending points of the edges
		height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	UCHAR  *dest_addr;

	if (y1==y2 || y1==y3)
		return;

	// test order of x1 and x2
	if (x3 < x2)
	{
		temp_x = x2;
		x2     = x3;
		x3     = temp_x;

	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = ((x2-x1)<<FIXP16_SHIFT)/height;
	dx_right = ((x3-x1)<<FIXP16_SHIFT)/height;

	// set starting points
	xs = (x1<<FIXP16_SHIFT);
	xe = (x1<<FIXP16_SHIFT); 

	// perform y clipping
	if (y1<min_clip_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(-y1+min_clip_y);
		xe = xe+dx_right*(-y1+min_clip_y);

		// reset y1
		y1=min_clip_y;

	} // end if top is off screen

	if (y3>max_clip_y)
		y3=max_clip_y;

	// compute starting address in video memory
	dest_addr = dest_buffer+y1*mempitch;

	// test if x clipping is needed
	if (x1>=min_clip_x && x1<=max_clip_x &&
		x2>=min_clip_x && x2<=max_clip_x &&
		x3>=min_clip_x && x3<=max_clip_x)
	{
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			memset((UCHAR *)dest_addr+((xs+FIXP16_ROUND_UP)>>FIXP16_SHIFT),
				color, (((xe-xs+FIXP16_ROUND_UP)>>FIXP16_SHIFT)+1));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

		} // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version

		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=mempitch)
		{
			// do x clip
			left  = ((xs+FIXP16_ROUND_UP)>>FIXP16_SHIFT);
			right = ((xe+FIXP16_ROUND_UP)>>FIXP16_SHIFT);

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < min_clip_x)
			{
				left = min_clip_x;

				if (right < min_clip_x)
					continue;
			}

			if (right > max_clip_x)
			{
				right = max_clip_x;

				if (left > max_clip_x)
					continue;
			}

			memset((UCHAR *)dest_addr+left,
				color, (right-left+1));

		} // end for

	} // end else x clipping needed

} // end Draw_Bottom_TriFP

////////////////////////////////////////////////////////////////////////////

int Fast_Distance_2D(int x, int y)
{
	// this function computes the distance from 0,0 to x,y with 3.5% error

	// first compute the absolute value of x,y
	x = abs(x);
	y = abs(y);

	// compute the minimum of x,y
	int mn = MIN(x,y);

	// return the distance
	return(x+y-(mn>>1)-(mn>>2)+(mn>>4));

} // end Fast_Distance_2D

///////////////////////////////////////////////////////////////////////////////

float Fast_Distance_3D(float fx, float fy, float fz)
{
	// this function computes the distance from the origin to x,y,z

	int temp;  // used for swaping
	int x,y,z; // used for algorithm

	// make sure values are all positive
	x = fabs(fx) * 1024;
	y = fabs(fy) * 1024;
	z = fabs(fz) * 1024;

	// sort values
	if (y < x) SWAP(x,y,temp)

		if (z < y) SWAP(y,z,temp)

			if (y < x) SWAP(x,y,temp)

				int dist = (z + 11 * (y >> 5) + (x >> 2) );

	// compute distance with 8% error
	return((float)(dist >> 10));

} // end Fast_Distance_3D

///////////////////////////////////////////////////////////////////////////////

int Find_Bounding_Box_Poly2D(POLYGON2D_PTR poly, 
							 float &min_x, float &max_x, 
							 float &min_y, float &max_y)
{
	// this function finds the bounding box of a 2D polygon 
	// and returns the values in the sent vars

	// is this poly valid?
	if (poly->num_verts == 0)
		return(0);

	// initialize output vars (note they are pointers)
	// also note that the algorithm assumes local coordinates
	// that is, the poly verts are relative to 0,0
	max_x = max_y = min_x = min_y = 0;

	// process each vertex
	for (int index=0; index < poly->num_verts; index++)
	{
		// update vars - run min/max seek
		if (poly->vlist[index].x > max_x)
			max_x = poly->vlist[index].x;

		if (poly->vlist[index].x < min_x)
			min_x = poly->vlist[index].x;

		if (poly->vlist[index].y > max_y)
			max_y = poly->vlist[index].y;

		if (poly->vlist[index].y < min_y)
			min_y = poly->vlist[index].y;

	} // end for index

	// return success
	return(1);

} // end Find_Bounding_Box_Poly2D

////////////////////////////////////////////////////////////////

void Draw_Filled_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int mempitch)
{
	// this function draws a general n sided polygon 

	int ydiff1, ydiff2,         // difference between starting x and ending x
		xdiff1, xdiff2,         // difference between starting y and ending y
		start,                  // starting offset of line between edges
		length,                 // distance from edge 1 to edge 2
		errorterm1, errorterm2, // error terms for edges 1 & 2
		offset1, offset2,       // offset of current pixel in edges 1 & 2
		count1, count2,         // increment count for edges 1 & 2
		xunit1, xunit2;         // unit to advance x offset for edges 1 & 2


#ifdef DEBUG_ON
	// track rendering stats
	debug_polys_rendered_per_frame++;
#endif

	// initialize count of number of edges drawn:
	int edgecount = poly->num_verts-1;

	// determine which vertex is at top of polygon:

	int firstvert=0;         // start by assuming vertex 0 is at top

	int min_y=poly->vlist[0].y; // find y coordinate of vertex 0

	for (int index=1; index < poly->num_verts; index++) 
	{  
		// Search thru vertices
		if ((poly->vlist[index].y) < min_y) 
		{  
			// is another vertex higher?
			firstvert=index;                   
			min_y=poly->vlist[index].y;
		} // end if

	} // end for index

	// finding starting and ending vertices of first two edges:
	int startvert1=firstvert;      // get starting vertex of edge 1
	int startvert2=firstvert;      // get starting vertex of edge 2
	int xstart1=poly->vlist[startvert1].x+poly->x0;
	int ystart1=poly->vlist[startvert1].y+poly->y0;
	int xstart2=poly->vlist[startvert2].x+poly->x0;
	int ystart2=poly->vlist[startvert2].y+poly->y0;
	int endvert1=startvert1-1;           // get ending vertex of edge 1

	if (endvert1 < 0) 
		endvert1=poly->num_verts-1;    // check for wrap

	int xend1=poly->vlist[endvert1].x+poly->x0;      // get x & y coordinates
	int yend1=poly->vlist[endvert1].y+poly->y0;      // of ending vertices
	int endvert2=startvert2+1;           // get ending vertex of edge 2

	if (endvert2==(poly->num_verts)) 
		endvert2=0;  // Check for wrap

	int xend2=poly->vlist[endvert2].x+poly->x0;      // get x & y coordinates
	int yend2=poly->vlist[endvert2].y+poly->y0;      // of ending vertices

	// draw the polygon:

	while (edgecount>0) 
	{    
		// continue drawing until all edges drawn
		offset1=mempitch*ystart1+xstart1;  // offset of edge 1
		offset2=mempitch*ystart2+xstart2;  // offset of edge 2

		// initialize error terms
		// for edges 1 & 2
		errorterm1=0;        
		errorterm2=0;           

		// get absolute value of
		if ((ydiff1=yend1-ystart1) < 0) 
			ydiff1=-ydiff1;

		// x & y lengths of edges
		if ((ydiff2=yend2-ystart2) < 0) 
			ydiff2=-ydiff2; 

		if ((xdiff1=xend1-xstart1) < 0) 
		{               
			// get value of length
			xunit1=-1;                    // calculate X increment
			xdiff1=-xdiff1;
		} // end if
		else 
		{
			xunit1=1;
		} // end else

		if ((xdiff2=xend2-xstart2) < 0) 
		{
			// Get value of length
			xunit2=-1;                   // calculate X increment
			xdiff2=-xdiff2;
		} // end else
		else 
		{
			xunit2=1;
		} // end else

		// choose which of four routines to use
		if (xdiff1 > ydiff1) 
		{    
			// if x length of edge 1 is greater than y length
			if (xdiff2 > ydiff2) 
			{  
				// if X length of edge 2 is greater than y length

				// increment edge 1 on X and edge 2 on X:
				count1=xdiff1;    // count for x increment on edge 1
				count2=xdiff2;    // count for x increment on edge 2

				while (count1 && count2) 
				{  
					// continue drawing until one edge is done
					// calculate edge 1:
					while ((errorterm1 < xdiff1) && (count1 > 0)) 
					{ 
						// finished w/edge 1?
						if (count1--) 
						{     
							// count down on edge 1
							offset1+=xunit1;  // increment pixel offset
							xstart1+=xunit1;
						} // end if

						errorterm1+=ydiff1; // increment error term

						if (errorterm1 < xdiff1) 
						{  // if not more than XDIFF
							vbuffer[offset1]=(UCHAR)poly->color; // ...plot a pixel
						} // end if

					} // end while

					errorterm1-=xdiff1; // if time to increment X, restore error term

					// calculate edge 2:

					while ((errorterm2 < xdiff2) && (count2 > 0)) 
					{  
						// finished w/edge 2?
						if (count2--) 
						{     
							// count down on edge 2
							offset2+=xunit2;  // increment pixel offset
							xstart2+=xunit2;
						} // end if

						errorterm2+=ydiff2; // increment error term

						if (errorterm2 < xdiff2) 
						{  // if not more than XDIFF
							vbuffer[offset2]=(UCHAR)poly->color;  // ...plot a pixel
						} // end if

					} // end while

					errorterm2-=xdiff2; // if time to increment X, restore error term

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0) 
					{ // if negative...
						length=-length;       // make it positive
						start=offset2;        // and set START to edge 2
					} // end if
					else 
						start=offset1;     // else set START to edge 1

					for (int index=start; index < start+length+1; index++)
					{  // From edge to edge...
						vbuffer[index]=(UCHAR)poly->color;         // ...draw the line
					} // end for index

					offset1+=mempitch;           // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;           // advance edge 2 offset to next line
					ystart2++;

				} // end if

			} // end if
			else 
			{
				// increment edge 1 on X and edge 2 on Y:
				count1=xdiff1;    // count for X increment on edge 1
				count2=ydiff2;    // count for Y increment on edge 2

				while (count1 && count2) 
				{  // continue drawing until one edge is done
					// calculate edge 1:
					while ((errorterm1 < xdiff1) && (count1 > 0)) 
					{ // finished w/edge 1?
						if (count1--) 
						{
							// count down on edge 1
							offset1+=xunit1;  // increment pixel offset
							xstart1+=xunit1;
						} // end if

						errorterm1+=ydiff1; // increment error term

						if (errorterm1 < xdiff1) 
						{  // If not more than XDIFF
							vbuffer[offset1]=(UCHAR)poly->color; // ...plot a pixel
						} // end if

					} // end while

					errorterm1-=xdiff1; // If time to increment X, restore error term

					// calculate edge 2:
					errorterm2+=xdiff2; // increment error term

					if (errorterm2 >= ydiff2)  
					{ // if time to increment Y...
						errorterm2-=ydiff2;        // ...restore error term
						offset2+=xunit2;           // ...and advance offset to next pixel
						xstart2+=xunit2;
					} // end if

					count2--;

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0)  
					{ // if negative...
						length=-length;       // ...make it positive
						start=offset2;        // and set START to edge 2
					} // end if
					else 
						start=offset1;        // else set START to edge 1

					for (int index=start; index < start+length+1; index++)  // from edge to edge
					{
						vbuffer[index]=(UCHAR)poly->color;         // ...draw the line
					} // end for index

					offset1+=mempitch;           // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;           // advance edge 2 offset to next line
					ystart2++;

				} // end while
			} // end if
		} // end if
		else 
		{
			if (xdiff2 > ydiff2) 
			{
				// increment edge 1 on Y and edge 2 on X:

				count1=ydiff1;  // count for Y increment on edge 1
				count2=xdiff2;  // count for X increment on edge 2

				while(count1 && count2) 
				{  // continue drawing until one edge is done
					// calculate edge 1:

					errorterm1+=xdiff1; // Increment error term

					if (errorterm1 >= ydiff1)  
					{  // if time to increment Y...
						errorterm1-=ydiff1;         // ...restore error term
						offset1+=xunit1;            // ...and advance offset to next pixel
						xstart1+=xunit1;
					} // end if

					count1--;

					// Calculate edge 2:

					while ((errorterm2 < xdiff2) && (count2 > 0)) 
					{ // finished w/edge 1?
						if (count2--) 
						{ // count down on edge 2
							offset2+=xunit2;  // increment pixel offset
							xstart2+=xunit2;
						} // end if

						errorterm2+=ydiff2; // increment error term

						if (errorterm2 < xdiff2) 
						{  // if not more than XDIFF
							vbuffer[offset2]=(UCHAR)poly->color; // ...plot a pixel
						} // end if
					} // end while

					errorterm2-=xdiff2;  // if time to increment X, restore error term

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0) 
					{    // if negative...
						length=-length;  // ...make it positive
						start=offset2;   // and set START to edge 2
					} // end if
					else 
						start=offset1;  // else set START to edge 1

					for (int index=start; index < start+length+1; index++) // from edge to edge...
					{
						vbuffer[index]=(UCHAR)poly->color;      // ...draw the line
					} // end for index

					offset1+=mempitch;         // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;         // advance edge 2 offset to next line
					ystart2++;

				} // end if
			} // end if
			else 
			{
				// increment edge 1 on Y and edge 2 on Y:
				count1=ydiff1;  // count for Y increment on edge 1
				count2=ydiff2;  // count for Y increment on edge 2

				while(count1 && count2) 
				{  
					// continue drawing until one edge is done
					// calculate edge 1:
					errorterm1+=xdiff1;  // increment error term

					if (errorterm1 >= ydiff1)  
					{                           // if time to increment Y
						errorterm1-=ydiff1;         // ...restore error term
						offset1+=xunit1;            // ...and advance offset to next pixel
						xstart1+=xunit1;
					} // end if

					count1--;

					// calculate edge 2:
					errorterm2+=xdiff2;            // increment error term

					if (errorterm2 >= ydiff2)  
					{                           // if time to increment Y
						errorterm2-=ydiff2;         // ...restore error term
						offset2+=xunit2;            // ...and advance offset to next pixel
						xstart2+=xunit2;
					} // end if

					--count2;

					// draw line from edge 1 to edge 2:

					length=offset2-offset1;  // determine length of horizontal line

					if (length < 0) 
					{          
						// if negative...
						length=-length;        // ...make it positive
						start=offset2;         // and set START to edge 2
					} // end if
					else 
						start=offset1;         // else set START to edge 1

					for (int index=start; index < start+length+1; index++)   
					{ // from edge to edge
						vbuffer[index]=(UCHAR)poly->color;   // ...draw the linee
					} // end for index

					offset1+=mempitch;            // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;            // advance edge 2 offset to next line
					ystart2++;

				} // end while

			} // end else

		} // end if

		// another edge (at least) is complete. Start next edge, if any.
		if (!count1) 
		{                      // if edge 1 is complete...
			--edgecount;           // decrement the edge count
			startvert1=endvert1;   // make ending vertex into start vertex
			--endvert1;            // and get new ending vertex

			if (endvert1 < 0) 
				endvert1=poly->num_verts-1; // check for wrap

			xend1=poly->vlist[endvert1].x+poly->x0;  // get x & y of new end vertex
			yend1=poly->vlist[endvert1].y+poly->y0;
		} // end if

		if (!count2) 
		{                     // if edge 2 is complete...
			--edgecount;          // decrement the edge count
			startvert2=endvert2;  // make ending vertex into start vertex
			endvert2++;           // and get new ending vertex

			if (endvert2==(poly->num_verts)) 
				endvert2=0;                // check for wrap

			xend2=poly->vlist[endvert2].x+poly->x0;  // get x & y of new end vertex
			yend2=poly->vlist[endvert2].y+poly->y0;

		} // end if

	} // end while

} // end Draw_Filled_Polygon2D

///////////////////////////////////////////////////////////////

void Draw_Filled_Polygon2D16(POLYGON2D_PTR poly, UCHAR *_vbuffer, int mempitch)
{
	// this function draws a general n sided polygon 

	int ydiff1, ydiff2,         // difference between starting x and ending x
		xdiff1, xdiff2,         // difference between starting y and ending y
		start,                  // starting offset of line between edges
		length,                 // distance from edge 1 to edge 2
		errorterm1, errorterm2, // error terms for edges 1 & 2
		offset1, offset2,       // offset of current pixel in edges 1 & 2
		count1, count2,         // increment count for edges 1 & 2
		xunit1, xunit2;         // unit to advance x offset for edges 1 & 2


#ifdef DEBUG_ON
	// track rendering stats
	debug_polys_rendered_per_frame++;
#endif


	// recast vbuffer into short version since this is a 16 bit mode
	USHORT *vbuffer = (USHORT *)_vbuffer;

	// convert mempitch into WORD or 16bit stride
	mempitch = (mempitch >> 1);

	// initialize count of number of edges drawn:
	int edgecount = poly->num_verts-1;

	// determine which vertex is at top of polygon:

	int firstvert=0;         // start by assuming vertex 0 is at top

	int min_y=poly->vlist[0].y; // find y coordinate of vertex 0

	for (int index=1; index < poly->num_verts; index++) 
	{  
		// Search thru vertices
		if ((poly->vlist[index].y) < min_y) 
		{  
			// is another vertex higher?
			firstvert=index;                   
			min_y=poly->vlist[index].y;
		} // end if

	} // end for index

	// finding starting and ending vertices of first two edges:
	int startvert1=firstvert;      // get starting vertex of edge 1
	int startvert2=firstvert;      // get starting vertex of edge 2
	int xstart1=poly->vlist[startvert1].x+poly->x0;
	int ystart1=poly->vlist[startvert1].y+poly->y0;
	int xstart2=poly->vlist[startvert2].x+poly->x0;
	int ystart2=poly->vlist[startvert2].y+poly->y0;
	int endvert1=startvert1-1;           // get ending vertex of edge 1

	if (endvert1 < 0) 
		endvert1=poly->num_verts-1;    // check for wrap

	int xend1=poly->vlist[endvert1].x+poly->x0;      // get x & y coordinates
	int yend1=poly->vlist[endvert1].y+poly->y0;      // of ending vertices
	int endvert2=startvert2+1;           // get ending vertex of edge 2

	if (endvert2==(poly->num_verts)) 
		endvert2=0;  // Check for wrap

	int xend2=poly->vlist[endvert2].x+poly->x0;      // get x & y coordinates
	int yend2=poly->vlist[endvert2].y+poly->y0;      // of ending vertices

	// draw the polygon:

	while (edgecount>0) 
	{    
		// continue drawing until all edges drawn
		offset1=mempitch*ystart1+xstart1;  // offset of edge 1
		offset2=mempitch*ystart2+xstart2;  // offset of edge 2

		// initialize error terms
		// for edges 1 & 2
		errorterm1=0;        
		errorterm2=0;           

		// get absolute value of
		if ((ydiff1=yend1-ystart1) < 0) 
			ydiff1=-ydiff1;

		// x & y lengths of edges
		if ((ydiff2=yend2-ystart2) < 0) 
			ydiff2=-ydiff2; 

		if ((xdiff1=xend1-xstart1) < 0) 
		{               
			// get value of length
			xunit1=-1;                    // calculate X increment
			xdiff1=-xdiff1;
		} // end if
		else 
		{
			xunit1=1;
		} // end else

		if ((xdiff2=xend2-xstart2) < 0) 
		{
			// Get value of length
			xunit2=-1;                   // calculate X increment
			xdiff2=-xdiff2;
		} // end else
		else 
		{
			xunit2=1;
		} // end else

		// choose which of four routines to use
		if (xdiff1 > ydiff1) 
		{    
			// if x length of edge 1 is greater than y length
			if (xdiff2 > ydiff2) 
			{  
				// if X length of edge 2 is greater than y length

				// increment edge 1 on X and edge 2 on X:
				count1=xdiff1;    // count for x increment on edge 1
				count2=xdiff2;    // count for x increment on edge 2

				while (count1 && count2) 
				{  
					// continue drawing until one edge is done
					// calculate edge 1:
					while ((errorterm1 < xdiff1) && (count1 > 0)) 
					{ 
						// finished w/edge 1?
						if (count1--) 
						{     
							// count down on edge 1
							offset1+=xunit1;  // increment pixel offset
							xstart1+=xunit1;
						} // end if

						errorterm1+=ydiff1; // increment error term

						if (errorterm1 < xdiff1) 
						{  // if not more than XDIFF
							vbuffer[offset1]=(USHORT)poly->color; // ...plot a pixel
						} // end if

					} // end while

					errorterm1-=xdiff1; // if time to increment X, restore error term

					// calculate edge 2:

					while ((errorterm2 < xdiff2) && (count2 > 0)) 
					{  
						// finished w/edge 2?
						if (count2--) 
						{     
							// count down on edge 2
							offset2+=xunit2;  // increment pixel offset
							xstart2+=xunit2;
						} // end if

						errorterm2+=ydiff2; // increment error term

						if (errorterm2 < xdiff2) 
						{  // if not more than XDIFF
							vbuffer[offset2]=(USHORT)poly->color;  // ...plot a pixel
						} // end if

					} // end while

					errorterm2-=xdiff2; // if time to increment X, restore error term

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0) 
					{ // if negative...
						length=-length;       // make it positive
						start=offset2;        // and set START to edge 2
					} // end if
					else 
						start=offset1;     // else set START to edge 1

					for (int index=start; index < start+length+1; index++)
					{  // From edge to edge...
						vbuffer[index]=(USHORT)poly->color;         // ...draw the line
					} // end for index

					offset1+=mempitch;           // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;           // advance edge 2 offset to next line
					ystart2++;

				} // end if

			} // end if
			else 
			{
				// increment edge 1 on X and edge 2 on Y:
				count1=xdiff1;    // count for X increment on edge 1
				count2=ydiff2;    // count for Y increment on edge 2

				while (count1 && count2) 
				{  // continue drawing until one edge is done
					// calculate edge 1:
					while ((errorterm1 < xdiff1) && (count1 > 0)) 
					{ // finished w/edge 1?
						if (count1--) 
						{
							// count down on edge 1
							offset1+=xunit1;  // increment pixel offset
							xstart1+=xunit1;
						} // end if

						errorterm1+=ydiff1; // increment error term

						if (errorterm1 < xdiff1) 
						{  // If not more than XDIFF
							vbuffer[offset1]=(USHORT)poly->color; // ...plot a pixel
						} // end if

					} // end while

					errorterm1-=xdiff1; // If time to increment X, restore error term

					// calculate edge 2:
					errorterm2+=xdiff2; // increment error term

					if (errorterm2 >= ydiff2)  
					{ // if time to increment Y...
						errorterm2-=ydiff2;        // ...restore error term
						offset2+=xunit2;           // ...and advance offset to next pixel
						xstart2+=xunit2;
					} // end if

					count2--;

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0)  
					{ // if negative...
						length=-length;       // ...make it positive
						start=offset2;        // and set START to edge 2
					} // end if
					else 
						start=offset1;        // else set START to edge 1

					for (int index=start; index < start+length+1; index++)  // from edge to edge
					{
						vbuffer[index]=(USHORT)poly->color;         // ...draw the line
					} // end for index

					offset1+=mempitch;           // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;           // advance edge 2 offset to next line
					ystart2++;

				} // end while
			} // end if
		} // end if
		else 
		{
			if (xdiff2 > ydiff2) 
			{
				// increment edge 1 on Y and edge 2 on X:

				count1=ydiff1;  // count for Y increment on edge 1
				count2=xdiff2;  // count for X increment on edge 2

				while(count1 && count2) 
				{  // continue drawing until one edge is done
					// calculate edge 1:

					errorterm1+=xdiff1; // Increment error term

					if (errorterm1 >= ydiff1)  
					{  // if time to increment Y...
						errorterm1-=ydiff1;         // ...restore error term
						offset1+=xunit1;            // ...and advance offset to next pixel
						xstart1+=xunit1;
					} // end if

					count1--;

					// Calculate edge 2:

					while ((errorterm2 < xdiff2) && (count2 > 0)) 
					{ // finished w/edge 1?
						if (count2--) 
						{ // count down on edge 2
							offset2+=xunit2;  // increment pixel offset
							xstart2+=xunit2;
						} // end if

						errorterm2+=ydiff2; // increment error term

						if (errorterm2 < xdiff2) 
						{  // if not more than XDIFF
							vbuffer[offset2]=(USHORT)poly->color; // ...plot a pixel
						} // end if
					} // end while

					errorterm2-=xdiff2;  // if time to increment X, restore error term

					// draw line from edge 1 to edge 2:

					length=offset2-offset1; // determine length of horizontal line

					if (length < 0) 
					{    // if negative...
						length=-length;  // ...make it positive
						start=offset2;   // and set START to edge 2
					} // end if
					else 
						start=offset1;  // else set START to edge 1

					for (int index=start; index < start+length+1; index++) // from edge to edge...
					{
						vbuffer[index]=(USHORT)poly->color;      // ...draw the line
					} // end for index

					offset1+=mempitch;         // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;         // advance edge 2 offset to next line
					ystart2++;

				} // end if
			} // end if
			else 
			{
				// increment edge 1 on Y and edge 2 on Y:
				count1=ydiff1;  // count for Y increment on edge 1
				count2=ydiff2;  // count for Y increment on edge 2

				while(count1 && count2) 
				{  
					// continue drawing until one edge is done
					// calculate edge 1:
					errorterm1+=xdiff1;  // increment error term

					if (errorterm1 >= ydiff1)  
					{                           // if time to increment Y
						errorterm1-=ydiff1;         // ...restore error term
						offset1+=xunit1;            // ...and advance offset to next pixel
						xstart1+=xunit1;
					} // end if

					count1--;

					// calculate edge 2:
					errorterm2+=xdiff2;            // increment error term

					if (errorterm2 >= ydiff2)  
					{                           // if time to increment Y
						errorterm2-=ydiff2;         // ...restore error term
						offset2+=xunit2;            // ...and advance offset to next pixel
						xstart2+=xunit2;
					} // end if

					--count2;

					// draw line from edge 1 to edge 2:

					length=offset2-offset1;  // determine length of horizontal line

					if (length < 0) 
					{          
						// if negative...
						length=-length;        // ...make it positive
						start=offset2;         // and set START to edge 2
					} // end if
					else 
						start=offset1;         // else set START to edge 1

					for (int index=start; index < start+length+1; index++)   
					{ // from edge to edge
						vbuffer[index]=(USHORT)poly->color;   // ...draw the linee
					} // end for index

					offset1+=mempitch;            // advance edge 1 offset to next line
					ystart1++;
					offset2+=mempitch;            // advance edge 2 offset to next line
					ystart2++;

				} // end while

			} // end else

		} // end if

		// another edge (at least) is complete. Start next edge, if any.
		if (!count1) 
		{                      // if edge 1 is complete...
			--edgecount;           // decrement the edge count
			startvert1=endvert1;   // make ending vertex into start vertex
			--endvert1;            // and get new ending vertex

			if (endvert1 < 0) 
				endvert1=poly->num_verts-1; // check for wrap

			xend1=poly->vlist[endvert1].x+poly->x0;  // get x & y of new end vertex
			yend1=poly->vlist[endvert1].y+poly->y0;
		} // end if

		if (!count2) 
		{                     // if edge 2 is complete...
			--edgecount;          // decrement the edge count
			startvert2=endvert2;  // make ending vertex into start vertex
			endvert2++;           // and get new ending vertex

			if (endvert2==(poly->num_verts)) 
				endvert2=0;                // check for wrap

			xend2=poly->vlist[endvert2].x+poly->x0;  // get x & y of new end vertex
			yend2=poly->vlist[endvert2].y+poly->y0;

		} // end if

	} // end while

} // end Draw_Filled_Polygon2D16

///////////////////////////////////////////////////////////////

void Build_Sin_Cos_Tables(void)
{

	// create sin/cos lookup table
	// note the creation of one extra element; 360
	// this helps with logic in using the tables

	// generate the tables 0 - 360 inclusive
	for (int ang = 0; ang <= 360; ang++)
	{
		// convert ang to radians
		float theta = (float)ang*PI/(float)180;

		// insert next entry into table
		cos_look[ang] = cos(theta);
		sin_look[ang] = sin(theta);

	} // end for ang

} // end Build_Sin_Cos_Tables

//////////////////////////////////////////////////////////////

int Translate_Polygon2D(POLYGON2D_PTR poly, int dx, int dy)
{
	// this function translates the center of a polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// translate
	poly->x0+=dx;
	poly->y0+=dy;

	// return success
	return(1);

} // end Translate_Polygon2D

///////////////////////////////////////////////////////////////

int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta)
{
	// this function rotates the local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// test for negative rotation angle
	if (theta < 0)
		theta+=360;

	// loop and rotate each point, very crude, no lookup!!!
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{

		// perform rotation
		float xr = (float)poly->vlist[curr_vert].x*cos_look[theta] - 
			(float)poly->vlist[curr_vert].y*sin_look[theta];

		float yr = (float)poly->vlist[curr_vert].x*sin_look[theta] + 
			(float)poly->vlist[curr_vert].y*cos_look[theta];

		// store result back
		poly->vlist[curr_vert].x = xr;
		poly->vlist[curr_vert].y = yr;

	} // end for curr_vert

	// return success
	return(1);

} // end Rotate_Polygon2D

////////////////////////////////////////////////////////

int Scale_Polygon2D(POLYGON2D_PTR poly, float sx, float sy)
{
	// this function scalesthe local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// loop and scale each point
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// scale and store result back
		poly->vlist[curr_vert].x *= sx;
		poly->vlist[curr_vert].y *= sy;

	} // end for curr_vert

	// return success
	return(1);

} // end Scale_Polygon2D

///////////////////////////////////////////////////////////

int Draw_Polygon2D16(POLYGON2D_PTR poly, UCHAR *vbuffer, int lpitch)
{
	// this function draws a POLYGON2D based on 

	// test if the polygon is visible
	if (poly->state)
	{
		// loop thru and draw a line from vertices 1 to n
		for (int index=0; index < poly->num_verts-1; index++)
		{
			// draw line from ith to ith+1 vertex
			Draw_Clip_Line16(poly->vlist[index].x+poly->x0, 
				poly->vlist[index].y+poly->y0,
				poly->vlist[index+1].x+poly->x0, 
				poly->vlist[index+1].y+poly->y0,
				poly->color,
				vbuffer, lpitch);

		} // end for

		// now close up polygon
		// draw line from last vertex to 0th
		Draw_Clip_Line16(poly->vlist[0].x+poly->x0, 
			poly->vlist[0].y+poly->y0,
			poly->vlist[poly->num_verts-1].x+poly->x0, 
			poly->vlist[poly->num_verts-1].y+poly->y0,
			poly->color,
			vbuffer, lpitch);

		// return success
		return(1);
	} // end if
	else 
		return(0);

} // end Draw_Polygon2D16

///////////////////////////////////////////////////////////

int Draw_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int lpitch)
{
	// this function draws a POLYGON2D based on 

	// test if the polygon is visible
	if (poly->state)
	{
		// loop thru and draw a line from vertices 1 to n
		for (int index=0; index < poly->num_verts-1; index++)
		{
			// draw line from ith to ith+1 vertex
			Draw_Clip_Line(poly->vlist[index].x+poly->x0, 
				poly->vlist[index].y+poly->y0,
				poly->vlist[index+1].x+poly->x0, 
				poly->vlist[index+1].y+poly->y0,
				poly->color,
				vbuffer, lpitch);

		} // end for

		// now close up polygon
		// draw line from last vertex to 0th
		Draw_Clip_Line(poly->vlist[0].x+poly->x0, 
			poly->vlist[0].y+poly->y0,
			poly->vlist[poly->num_verts-1].x+poly->x0, 
			poly->vlist[poly->num_verts-1].y+poly->y0,
			poly->color,
			vbuffer, lpitch);

		// return success
		return(1);
	} // end if
	else 
		return(0);

} // end Draw_Polygon2D

///////////////////////////////////////////////////////////////

// these are the matrix versions, note they are more inefficient for
// single transforms, but their power comes into play when you concatenate
// multiple transformations, not to mention that all transforms are accomplished
// with the same code, just the matrix differs

int Translate_Polygon2D_Mat(POLYGON2D_PTR poly, int dx, int dy)
{
	// this function translates the center of a polygon by using a matrix multiply
	// on the the center point, this is incredibly inefficient, but for educational purposes
	// if we had an object that wasn't in local coordinates then it would make more sense to
	// use a matrix, but since the origin of the object is at x0,y0 then 2 lines of code can
	// translate, but lets do it the hard way just to see :)

	// test for valid pointer
	if (!poly)
		return(0);

	MATRIX3X2 mt; // used to hold translation transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&mt,1,0, 0,1, dx, dy); 

	// create a 1x2 matrix to do the transform
	MATRIX1X2 p0 = {poly->x0, poly->y0};
	MATRIX1X2 p1 = {0,0}; // this will hold result

	// now translate via a matrix multiply
	Mat_Mul_1X2_3X2(&p0, &mt, &p1);

	// now copy the result back into polygon
	poly->x0 = p1.M[0];
	poly->y0 = p1.M[1];

	// return success
	return(1);

} // end Translate_Polygon2D_Mat

///////////////////////////////////////////////////////////////

int Rotate_Polygon2D_Mat(POLYGON2D_PTR poly, int theta)
{
	// this function rotates the local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// test for negative rotation angle
	if (theta < 0)
		theta+=360;

	MATRIX3X2 mr; // used to hold rotation transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&mr,cos_look[theta],sin_look[theta], 
		-sin_look[theta],cos_look[theta], 
		0, 0); 

	// loop and rotate each point, very crude, no lookup!!!
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// create a 1x2 matrix to do the transform
		MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y};
		MATRIX1X2 p1 = {0,0}; // this will hold result

		// now rotate via a matrix multiply
		Mat_Mul_1X2_3X2(&p0, &mr, &p1);

		// now copy the result back into vertex
		poly->vlist[curr_vert].x = p1.M[0];
		poly->vlist[curr_vert].y = p1.M[1];

	} // end for curr_vert

	// return success
	return(1);

} // end Rotate_Polygon2D_Mat

////////////////////////////////////////////////////////

int Scale_Polygon2D_Mat(POLYGON2D_PTR poly, float sx, float sy)
{
	// this function scalesthe local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);


	MATRIX3X2 ms; // used to hold scaling transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&ms,sx,0, 
		0,sy, 
		0, 0); 


	// loop and scale each point
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// scale and store result back

		// create a 1x2 matrix to do the transform
		MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y};
		MATRIX1X2 p1 = {0,0}; // this will hold result

		// now scale via a matrix multiply
		Mat_Mul_1X2_3X2(&p0, &ms, &p1);

		// now copy the result back into vertex
		poly->vlist[curr_vert].x = p1.M[0];
		poly->vlist[curr_vert].y = p1.M[1];

	} // end for curr_vert

	// return success
	return(1);

} // end Scale_Polygon2D_Mat

///////////////////////////////////////////////////////////

int Mat_Mul_3X3(MATRIX3X3_PTR ma, 
				MATRIX3X3_PTR mb,
				MATRIX3X3_PTR mprod)
{
	// this function multiplies two matrices together and 
	// and stores the result

	for (int row=0; row<3; row++)
	{
		for (int col=0; col<3; col++)
		{
			// compute dot product from row of ma 
			// and column of mb

			float sum = 0; // used to hold result

			for (int index=0; index<3; index++)
			{
				// add in next product pair
				sum+=(ma->M[row][index]*mb->M[index][col]);
			} // end for index

			// insert resulting row,col element
			mprod->M[row][col] = sum;

		} // end for col

	} // end for row

	return(1);

} // end Mat_Mul_3X3

////////////////////////////////////////////////////////////////

int Mat_Mul_1X3_3X3(MATRIX1X3_PTR ma, 
					MATRIX3X3_PTR mb,
					MATRIX1X3_PTR mprod)
{
	// this function multiplies a 1x3 matrix against a 
	// 3x3 matrix - ma*mb and stores the result

	for (int col=0; col<3; col++)
	{
		// compute dot product from row of ma 
		// and column of mb

		float sum = 0; // used to hold result

		for (int index=0; index<3; index++)
		{
			// add in next product pair
			sum+=(ma->M[index]*mb->M[index][col]);
		} // end for index

		// insert resulting col element
		mprod->M[col] = sum;

	} // end for col

	return(1);

} // end Mat_Mul_1X3_3X3

////////////////////////////////////////////////////////////////

int Mat_Mul_1X2_3X2(MATRIX1X2_PTR ma, 
					MATRIX3X2_PTR mb,
					MATRIX1X2_PTR mprod)
{
	// this function multiplies a 1x2 matrix against a 
	// 3x2 matrix - ma*mb and stores the result
	// using a dummy element for the 3rd element of the 1x2 
	// to make the matrix multiply valid i.e. 1x3 X 3x2

	for (int col=0; col<2; col++)
	{
		// compute dot product from row of ma 
		// and column of mb

		float sum = 0; // used to hold result

		for (int index=0; index<2; index++)
		{
			// add in next product pair
			sum+=(ma->M[index]*mb->M[index][col]);
		} // end for index

		// add in last element * 1 
		sum+= mb->M[2][col];

		// insert resulting col element
		mprod->M[col] = sum;

	} // end for col

	return(1);

} // end Mat_Mul_1X2_3X2

//////////////////////////////////////////////////////////////
#if 0
inline int Mat_Init_3X2(MATRIX3X2_PTR ma, 
						float m00, float m01,
						float m10, float m11,
						float m20, float m21)
{
	// this function fills a 3x2 matrix with the sent data in row major form
	ma->M[0][0] = m00; ma->M[0][1] = m01; 
	ma->M[1][0] = m10; ma->M[1][1] = m11; 
	ma->M[2][0] = m20; ma->M[2][1] = m21; 

	// return success
	return(1);

} // end Mat_Init_3X2
#endif
/////////////////////////////////////////////////////////////////